sequel 3.21.0 → 3.31.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 (225) hide show
  1. data/CHANGELOG +485 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +37 -19
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +249 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/dataset_filtering.rdoc +8 -0
  8. data/doc/mass_assignment.rdoc +54 -0
  9. data/doc/migration.rdoc +15 -538
  10. data/doc/model_hooks.rdoc +72 -27
  11. data/doc/opening_databases.rdoc +89 -50
  12. data/doc/prepared_statements.rdoc +40 -13
  13. data/doc/reflection.rdoc +8 -2
  14. data/doc/release_notes/3.22.0.txt +39 -0
  15. data/doc/release_notes/3.23.0.txt +172 -0
  16. data/doc/release_notes/3.24.0.txt +420 -0
  17. data/doc/release_notes/3.25.0.txt +88 -0
  18. data/doc/release_notes/3.26.0.txt +88 -0
  19. data/doc/release_notes/3.27.0.txt +82 -0
  20. data/doc/release_notes/3.28.0.txt +304 -0
  21. data/doc/release_notes/3.29.0.txt +459 -0
  22. data/doc/release_notes/3.30.0.txt +135 -0
  23. data/doc/release_notes/3.31.0.txt +146 -0
  24. data/doc/schema_modification.rdoc +547 -0
  25. data/doc/sharding.rdoc +7 -1
  26. data/doc/testing.rdoc +129 -0
  27. data/doc/transactions.rdoc +137 -0
  28. data/doc/virtual_rows.rdoc +2 -2
  29. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  30. data/lib/sequel/adapters/ado.rb +40 -18
  31. data/lib/sequel/adapters/amalgalite.rb +17 -9
  32. data/lib/sequel/adapters/db2.rb +175 -86
  33. data/lib/sequel/adapters/dbi.rb +15 -15
  34. data/lib/sequel/adapters/do/mysql.rb +5 -7
  35. data/lib/sequel/adapters/do/postgres.rb +0 -5
  36. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/do.rb +17 -36
  38. data/lib/sequel/adapters/firebird.rb +27 -208
  39. data/lib/sequel/adapters/ibmdb.rb +448 -0
  40. data/lib/sequel/adapters/informix.rb +6 -23
  41. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  42. data/lib/sequel/adapters/jdbc/db2.rb +45 -0
  43. data/lib/sequel/adapters/jdbc/derby.rb +247 -0
  44. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  45. data/lib/sequel/adapters/jdbc/h2.rb +56 -29
  46. data/lib/sequel/adapters/jdbc/hsqldb.rb +170 -0
  47. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  48. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  49. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  50. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  51. data/lib/sequel/adapters/jdbc/oracle.rb +81 -25
  52. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  53. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  54. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  55. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  56. data/lib/sequel/adapters/jdbc.rb +231 -78
  57. data/lib/sequel/adapters/mock.rb +335 -0
  58. data/lib/sequel/adapters/mysql.rb +82 -184
  59. data/lib/sequel/adapters/mysql2.rb +26 -25
  60. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  61. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  62. data/lib/sequel/adapters/odbc.rb +28 -9
  63. data/lib/sequel/adapters/openbase.rb +3 -5
  64. data/lib/sequel/adapters/oracle.rb +351 -53
  65. data/lib/sequel/adapters/postgres.rb +194 -33
  66. data/lib/sequel/adapters/shared/access.rb +32 -9
  67. data/lib/sequel/adapters/shared/db2.rb +321 -0
  68. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  69. data/lib/sequel/adapters/shared/informix.rb +53 -0
  70. data/lib/sequel/adapters/shared/mssql.rb +231 -106
  71. data/lib/sequel/adapters/shared/mysql.rb +181 -63
  72. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +231 -41
  74. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  75. data/lib/sequel/adapters/shared/progress.rb +4 -7
  76. data/lib/sequel/adapters/shared/sqlite.rb +201 -57
  77. data/lib/sequel/adapters/sqlite.rb +29 -21
  78. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  79. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  80. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  81. data/lib/sequel/adapters/swift.rb +5 -5
  82. data/lib/sequel/adapters/tinytds.rb +160 -19
  83. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  84. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  85. data/lib/sequel/ast_transformer.rb +190 -0
  86. data/lib/sequel/connection_pool/threaded.rb +3 -2
  87. data/lib/sequel/connection_pool.rb +1 -1
  88. data/lib/sequel/core.rb +46 -5
  89. data/lib/sequel/database/connecting.rb +5 -6
  90. data/lib/sequel/database/dataset.rb +4 -4
  91. data/lib/sequel/database/dataset_defaults.rb +59 -1
  92. data/lib/sequel/database/logging.rb +1 -1
  93. data/lib/sequel/database/misc.rb +104 -19
  94. data/lib/sequel/database/query.rb +153 -58
  95. data/lib/sequel/database/schema_generator.rb +17 -7
  96. data/lib/sequel/database/schema_methods.rb +59 -29
  97. data/lib/sequel/dataset/actions.rb +231 -76
  98. data/lib/sequel/dataset/features.rb +91 -8
  99. data/lib/sequel/dataset/graph.rb +38 -11
  100. data/lib/sequel/dataset/misc.rb +50 -23
  101. data/lib/sequel/dataset/mutation.rb +5 -6
  102. data/lib/sequel/dataset/prepared_statements.rb +48 -27
  103. data/lib/sequel/dataset/query.rb +236 -43
  104. data/lib/sequel/dataset/sql.rb +665 -253
  105. data/lib/sequel/dataset.rb +8 -0
  106. data/lib/sequel/exceptions.rb +4 -0
  107. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  108. data/lib/sequel/extensions/migration.rb +6 -4
  109. data/lib/sequel/extensions/named_timezones.rb +5 -0
  110. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  111. data/lib/sequel/extensions/to_dot.rb +95 -83
  112. data/lib/sequel/model/associations.rb +1020 -337
  113. data/lib/sequel/model/base.rb +414 -132
  114. data/lib/sequel/model/errors.rb +1 -1
  115. data/lib/sequel/model/exceptions.rb +5 -1
  116. data/lib/sequel/model.rb +15 -8
  117. data/lib/sequel/plugins/association_pks.rb +22 -4
  118. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  119. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  120. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  121. data/lib/sequel/plugins/force_encoding.rb +6 -6
  122. data/lib/sequel/plugins/identity_map.rb +114 -7
  123. data/lib/sequel/plugins/many_through_many.rb +65 -7
  124. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  125. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  126. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  127. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  128. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  129. data/lib/sequel/plugins/serialization.rb +88 -44
  130. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  131. data/lib/sequel/plugins/sharding.rb +21 -54
  132. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  133. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  134. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  135. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  136. data/lib/sequel/sql.rb +109 -52
  137. data/lib/sequel/timezones.rb +57 -38
  138. data/lib/sequel/version.rb +1 -1
  139. data/spec/adapters/db2_spec.rb +146 -0
  140. data/spec/adapters/mssql_spec.rb +111 -29
  141. data/spec/adapters/mysql_spec.rb +135 -123
  142. data/spec/adapters/oracle_spec.rb +49 -77
  143. data/spec/adapters/postgres_spec.rb +267 -78
  144. data/spec/adapters/spec_helper.rb +6 -5
  145. data/spec/adapters/sqlite_spec.rb +35 -21
  146. data/spec/core/connection_pool_spec.rb +71 -92
  147. data/spec/core/core_sql_spec.rb +21 -30
  148. data/spec/core/database_spec.rb +788 -512
  149. data/spec/core/dataset_spec.rb +1073 -1053
  150. data/spec/core/expression_filters_spec.rb +159 -42
  151. data/spec/core/mock_adapter_spec.rb +419 -0
  152. data/spec/core/object_graph_spec.rb +86 -114
  153. data/spec/core/schema_generator_spec.rb +3 -3
  154. data/spec/core/schema_spec.rb +298 -38
  155. data/spec/core/spec_helper.rb +6 -48
  156. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  157. data/spec/extensions/association_dependencies_spec.rb +2 -12
  158. data/spec/extensions/association_pks_spec.rb +66 -32
  159. data/spec/extensions/caching_spec.rb +23 -50
  160. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  161. data/spec/extensions/columns_introspection_spec.rb +91 -0
  162. data/spec/extensions/composition_spec.rb +18 -13
  163. data/spec/extensions/dataset_associations_spec.rb +199 -0
  164. data/spec/extensions/defaults_setter_spec.rb +64 -0
  165. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  166. data/spec/extensions/identity_map_spec.rb +114 -22
  167. data/spec/extensions/instance_filters_spec.rb +11 -21
  168. data/spec/extensions/instance_hooks_spec.rb +53 -0
  169. data/spec/extensions/json_serializer_spec.rb +4 -5
  170. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  171. data/spec/extensions/list_spec.rb +17 -39
  172. data/spec/extensions/many_through_many_spec.rb +283 -250
  173. data/spec/extensions/migration_spec.rb +35 -32
  174. data/spec/extensions/named_timezones_spec.rb +23 -3
  175. data/spec/extensions/nested_attributes_spec.rb +97 -88
  176. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  177. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  178. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  179. data/spec/extensions/prepared_statements_spec.rb +53 -0
  180. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  181. data/spec/extensions/pretty_table_spec.rb +1 -6
  182. data/spec/extensions/rcte_tree_spec.rb +41 -43
  183. data/spec/extensions/schema_dumper_spec.rb +5 -8
  184. data/spec/extensions/schema_spec.rb +13 -21
  185. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  186. data/spec/extensions/serialization_spec.rb +72 -43
  187. data/spec/extensions/sharding_spec.rb +66 -140
  188. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  189. data/spec/extensions/spec_helper.rb +13 -61
  190. data/spec/extensions/sql_expr_spec.rb +20 -60
  191. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  192. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  193. data/spec/extensions/timestamps_spec.rb +6 -6
  194. data/spec/extensions/to_dot_spec.rb +4 -7
  195. data/spec/extensions/touch_spec.rb +13 -14
  196. data/spec/extensions/tree_spec.rb +11 -26
  197. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  198. data/spec/extensions/update_primary_key_spec.rb +30 -24
  199. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  200. data/spec/extensions/validation_helpers_spec.rb +16 -35
  201. data/spec/extensions/xml_serializer_spec.rb +16 -4
  202. data/spec/integration/associations_test.rb +537 -21
  203. data/spec/integration/database_test.rb +4 -2
  204. data/spec/integration/dataset_test.rb +461 -65
  205. data/spec/integration/eager_loader_test.rb +19 -21
  206. data/spec/integration/model_test.rb +80 -1
  207. data/spec/integration/plugin_test.rb +304 -116
  208. data/spec/integration/prepared_statement_test.rb +212 -122
  209. data/spec/integration/schema_test.rb +161 -30
  210. data/spec/integration/spec_helper.rb +39 -30
  211. data/spec/integration/timezone_test.rb +38 -12
  212. data/spec/integration/transaction_test.rb +172 -5
  213. data/spec/integration/type_test.rb +17 -3
  214. data/spec/model/association_reflection_spec.rb +201 -7
  215. data/spec/model/associations_spec.rb +865 -662
  216. data/spec/model/base_spec.rb +275 -95
  217. data/spec/model/dataset_methods_spec.rb +7 -27
  218. data/spec/model/eager_loading_spec.rb +677 -676
  219. data/spec/model/hooks_spec.rb +309 -67
  220. data/spec/model/model_spec.rb +222 -168
  221. data/spec/model/plugins_spec.rb +24 -13
  222. data/spec/model/record_spec.rb +381 -218
  223. data/spec/model/spec_helper.rb +13 -71
  224. data/spec/model/validations_spec.rb +11 -0
  225. metadata +115 -73
@@ -6,17 +6,9 @@ describe "AssociationDependencies plugin" do
6
6
  @c = Class.new(Sequel::Model)
7
7
  @c.plugin :association_dependencies
8
8
  @Artist = Class.new(@c).set_dataset(:artists)
9
- ds1 = @Artist.dataset
10
- def ds1.fetch_rows(s)
11
- (MODEL_DB.sqls ||= []) << s
12
- yield({:id=>2, :name=>'Ar'})
13
- end
9
+ @Artist.dataset._fetch = {:id=>2, :name=>'Ar'}
14
10
  @Album = Class.new(@c).set_dataset(:albums)
15
- ds1 = @Album.dataset
16
- def ds1.fetch_rows(s)
17
- (MODEL_DB.sqls ||= []) << s
18
- yield({:id=>1, :name=>'Al', :artist_id=>2})
19
- end
11
+ @Album.dataset._fetch = {:id=>1, :name=>'Al', :artist_id=>2}
20
12
  @Artist.columns :id, :name
21
13
  @Album.columns :id, :name, :artist_id
22
14
  @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
@@ -113,11 +105,9 @@ describe "AssociationDependencies plugin" do
113
105
  c.add_association_dependencies :artist=>:destroy
114
106
  c.load(:id=>1, :name=>'Al', :artist_id=>2).destroy
115
107
  MODEL_DB.sqls.should == ['DELETE FROM albums WHERE (id = 1)', 'SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1', 'DELETE FROM artists WHERE (id = 2)']
116
- MODEL_DB.reset
117
108
 
118
109
  @Album.load(:id=>1, :name=>'Al', :artist_id=>2).destroy
119
110
  MODEL_DB.sqls.should == ['DELETE FROM albums WHERE (id = 1)']
120
- MODEL_DB.reset
121
111
 
122
112
  @Album.add_association_dependencies :artist=>:destroy
123
113
  c2 = Class.new(@Album)
@@ -2,31 +2,18 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe "Sequel::Plugins::AssociationPks" do
4
4
  before do
5
- @db = MODEL_DB.clone
6
- mod = Module.new do
7
- def fetch_rows(sql)
8
- case sql
9
- when "SELECT id FROM albums WHERE (albums.artist_id = 1)"
10
- yield({:id=>1})
11
- yield({:id=>2})
12
- yield({:id=>3})
13
- when /SELECT tag_id FROM albums_tags WHERE \(album_id = (\d)\)/
14
- yield({:tag_id=>1}) if $1 == '1'
15
- yield({:tag_id=>2}) if $1 != '3'
16
- yield({:tag_id=>3}) if $1 == '2'
17
- end
5
+ @db = Sequel.mock(:fetch=>proc do |sql|
6
+ case sql
7
+ when "SELECT id FROM albums WHERE (albums.artist_id = 1)"
8
+ [{:id=>1}, {:id=>2}, {:id=>3}]
9
+ when /SELECT tag_id FROM albums_tags WHERE \(album_id = (\d)\)/
10
+ a = []
11
+ a << {:tag_id=>1} if $1 == '1'
12
+ a << {:tag_id=>2} if $1 != '3'
13
+ a << {:tag_id=>3} if $1 == '2'
14
+ a
18
15
  end
19
- end
20
- @db.meta_def(:dataset) do |*opts|
21
- ds = super(*opts)
22
- ds.extend mod
23
- ds
24
- end
25
- def @db.transaction(opts)
26
- execute('BEGIN')
27
- yield
28
- execute('COMMIT')
29
- end
16
+ end)
30
17
  @Artist = Class.new(Sequel::Model(@db[:artists]))
31
18
  @Artist.columns :id
32
19
  @Album= Class.new(Sequel::Model(@db[:albums]))
@@ -37,7 +24,7 @@ describe "Sequel::Plugins::AssociationPks" do
37
24
  @Album.plugin :association_pks
38
25
  @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
39
26
  @Album.many_to_many :tags, :class=>@Tag, :join_table=>:albums_tags, :left_key=>:album_id
40
- @db.reset
27
+ @db.sqls
41
28
  end
42
29
 
43
30
  specify "should return correct associated pks for one_to_many associations" do
@@ -57,10 +44,20 @@ describe "Sequel::Plugins::AssociationPks" do
57
44
  "UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (id NOT IN (1, 2)))"]
58
45
  end
59
46
 
47
+ specify "should use associated class's primary key for a one_to_many association" do
48
+ @Album.set_primary_key :foo
49
+ @Artist.load(:id=>1).album_pks = [1, 2]
50
+ @db.sqls.should == ["UPDATE albums SET artist_id = 1 WHERE (foo IN (1, 2))",
51
+ "UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (foo NOT IN (1, 2)))"]
52
+ end
53
+
60
54
  specify "should set associated pks correctly for a many_to_many association" do
61
55
  @Album.load(:id=>2).tag_pks = [1, 3]
62
- @db.sqls[0].should == "DELETE FROM albums_tags WHERE ((album_id = 2) AND (tag_id NOT IN (1, 3)))"
63
- @db.sqls[1].should =~ /INSERT INTO albums_tags \((album_id, tag_id|tag_id, album_id)\) VALUES \((2, 1|1, 2)\)/
56
+ sqls = @db.sqls
57
+ sqls[0].should == "DELETE FROM albums_tags WHERE ((album_id = 2) AND (tag_id NOT IN (1, 3)))"
58
+ sqls[1].should == 'SELECT tag_id FROM albums_tags WHERE (album_id = 2)'
59
+ sqls[2].should =~ /INSERT INTO albums_tags \((album_id, tag_id|tag_id, album_id)\) VALUES \((2, 1|1, 2)\)/
60
+ sqls.length.should == 3
64
61
  end
65
62
 
66
63
  specify "should use transactions if the object is configured to use transactions" do
@@ -71,15 +68,52 @@ describe "Sequel::Plugins::AssociationPks" do
71
68
  "UPDATE albums SET artist_id = 1 WHERE (id IN (1, 2))",
72
69
  "UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (id NOT IN (1, 2)))",
73
70
  "COMMIT"]
74
- @db.reset
75
71
 
76
72
  album = @Album.load(:id=>2)
77
73
  album.use_transactions = true
78
74
  album.tag_pks = [1, 3]
79
- @db.sqls[0].should == "BEGIN"
80
- @db.sqls[1].should == "DELETE FROM albums_tags WHERE ((album_id = 2) AND (tag_id NOT IN (1, 3)))"
81
- @db.sqls[2].should =~ /INSERT INTO albums_tags \((album_id, tag_id|tag_id, album_id)\) VALUES \((2, 1|1, 2)\)/
82
- @db.sqls[3].should == "COMMIT"
75
+ sqls = @db.sqls
76
+ sqls[0].should == "BEGIN"
77
+ sqls[1].should == "DELETE FROM albums_tags WHERE ((album_id = 2) AND (tag_id NOT IN (1, 3)))"
78
+ sqls[2].should == 'SELECT tag_id FROM albums_tags WHERE (album_id = 2)'
79
+ sqls[3].should =~ /INSERT INTO albums_tags \((album_id, tag_id|tag_id, album_id)\) VALUES \((2, 1|1, 2)\)/
80
+ sqls[4].should == "COMMIT"
81
+ sqls.length.should == 5
82
+ end
83
+
84
+ specify "should automatically convert keys to numbers if the primary key is an integer for one_to_many associations" do
85
+ @Album.db_schema[:id][:type] = :integer
86
+ @Artist.load(:id=>1).album_pks = %w'1 2'
87
+ @db.sqls.should == ["UPDATE albums SET artist_id = 1 WHERE (id IN (1, 2))",
88
+ "UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (id NOT IN (1, 2)))"]
89
+ end
90
+
91
+ specify "should not automatically convert keys if the primary key is not an integer for many_to_many associations" do
92
+ @Album.db_schema[:id][:type] = :string
93
+ @Artist.load(:id=>1).album_pks = %w'1 2'
94
+ @db.sqls.should == ["UPDATE albums SET artist_id = 1 WHERE (id IN ('1', '2'))",
95
+ "UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (id NOT IN ('1', '2')))"]
96
+ end
97
+
98
+ specify "should automatically convert keys to numbers if the primary key is an integer for one_to_many associations" do
99
+ @Tag.db_schema[:id][:type] = :integer
100
+ @Album.load(:id=>2).tag_pks = %w'1 3'
101
+ sqls = @db.sqls
102
+ sqls[0].should == "DELETE FROM albums_tags WHERE ((album_id = 2) AND (tag_id NOT IN (1, 3)))"
103
+ sqls[1].should == 'SELECT tag_id FROM albums_tags WHERE (album_id = 2)'
104
+ sqls[2].should =~ /INSERT INTO albums_tags \((album_id, tag_id|tag_id, album_id)\) VALUES \((2, 1|1, 2)\)/
105
+ sqls.length.should == 3
106
+ end
107
+
108
+ specify "should not automatically convert keys to numbers if the primary key is an integer for many_to_many associations" do
109
+ @Tag.db_schema[:id][:type] = :string
110
+ @Album.load(:id=>2).tag_pks = %w'1 3'
111
+ sqls = @db.sqls
112
+ sqls[0].should == "DELETE FROM albums_tags WHERE ((album_id = 2) AND (tag_id NOT IN ('1', '3')))"
113
+ sqls[1].should == 'SELECT tag_id FROM albums_tags WHERE (album_id = 2)'
114
+ sqls[2].should =~ /INSERT INTO albums_tags \((album_id, tag_id|tag_id, album_id)\) VALUES \((2, '1'|'1', 2)\)/
115
+ sqls[3].should =~ /INSERT INTO albums_tags \((album_id, tag_id|tag_id, album_id)\) VALUES \((2, '3'|'3', 2)\)/
116
+ sqls.length.should == 4
83
117
  end
84
118
 
85
119
  end
@@ -2,8 +2,6 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe Sequel::Model, "caching" do
4
4
  before do
5
- MODEL_DB.reset
6
-
7
5
  @cache_class = Class.new(Hash) do
8
6
  attr_accessor :ttl
9
7
  def set(k, v, ttl); self[k] = v; @ttl = ttl; end
@@ -44,31 +42,14 @@ describe Sequel::Model, "caching" do
44
42
  columns :name, :id
45
43
  end
46
44
 
47
-
48
- $cache_dataset_row = {:name => 'sharon', :id => 1}
49
45
  @dataset = @c.dataset = @c3.dataset = @c4.dataset
50
- $sqls = []
51
- @dataset.extend(Module.new {
52
- def fetch_rows(sql)
53
- $sqls << sql
54
- yield $cache_dataset_row
55
- end
56
-
57
- def update(values)
58
- $sqls << update_sql(values)
59
- $cache_dataset_row.merge!(values)
60
- 1
61
- end
62
-
63
- def delete
64
- $sqls << delete_sql
65
- 1
66
- end
67
- })
46
+ @dataset._fetch = {:name => 'sharon', :id => 1}
47
+ @dataset.numrows = 1
68
48
 
69
49
  @c2 = Class.new(@c) do
70
50
  def self.name; 'SubItem' end
71
51
  end
52
+ @c.db.reset
72
53
  end
73
54
 
74
55
  it "should set the model's cache store" do
@@ -154,27 +135,26 @@ describe Sequel::Model, "caching" do
154
135
  end
155
136
 
156
137
  it "should set the cache when reading from the database" do
157
- $sqls.should == []
138
+ @c.db.sqls.should == []
158
139
  @cache.should be_empty
159
140
 
160
141
  m = @c[1]
161
- $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
162
- m.values.should == $cache_dataset_row
142
+ @c.db.sqls.should == ['SELECT * FROM items WHERE id = 1']
143
+ m.values.should == {:name=>"sharon", :id=>1}
163
144
  @cache[m.cache_key].should == m
164
145
  m2 = @c[1]
165
- $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
146
+ @c.db.sqls.should == []
166
147
  m2.should == m
167
- m2.values.should == $cache_dataset_row
148
+ m2.values.should == {:name=>"sharon", :id=>1}
168
149
 
169
- $sqls.clear
170
150
  m = @c2[1]
171
- $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
172
- m.values.should == $cache_dataset_row
151
+ @c.db.sqls.should == ['SELECT * FROM items WHERE id = 1']
152
+ m.values.should == {:name=>"sharon", :id=>1}
173
153
  @cache[m.cache_key].should == m
174
154
  m2 = @c2[1]
175
- $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
155
+ @c.db.sqls.should == []
176
156
  m2.should == m
177
- m2.values.should == $cache_dataset_row
157
+ m2.values.should == {:name=>"sharon", :id=>1}
178
158
  end
179
159
 
180
160
  it "should delete the cache when writing to the database" do
@@ -183,14 +163,14 @@ describe Sequel::Model, "caching" do
183
163
  m.name = 'hey'
184
164
  m.save
185
165
  @cache.has_key?(m.cache_key).should be_false
186
- $sqls.last.should == "UPDATE items SET name = 'hey' WHERE (id = 1)"
166
+ @c.db.sqls.should == ["SELECT * FROM items WHERE id = 1", "UPDATE items SET name = 'hey' WHERE (id = 1)"]
187
167
 
188
168
  m = @c2[1]
189
169
  @cache[m.cache_key].should == m
190
170
  m.name = 'hey'
191
171
  m.save
192
172
  @cache.has_key?(m.cache_key).should be_false
193
- $sqls.last.should == "UPDATE items SET name = 'hey' WHERE (id = 1)"
173
+ @c.db.sqls.should == ["SELECT * FROM items WHERE id = 1", "UPDATE items SET name = 'hey' WHERE (id = 1)"]
194
174
  end
195
175
 
196
176
  it "should delete the cache when deleting the record" do
@@ -198,40 +178,33 @@ describe Sequel::Model, "caching" do
198
178
  @cache[m.cache_key].should == m
199
179
  m.delete
200
180
  @cache.has_key?(m.cache_key).should be_false
201
- $sqls.last.should == "DELETE FROM items WHERE (id = 1)"
181
+ @c.db.sqls.should == ["SELECT * FROM items WHERE id = 1", "DELETE FROM items WHERE (id = 1)"]
202
182
 
203
183
  m = @c2[1]
204
184
  @cache[m.cache_key].should == m
205
185
  m.delete
206
186
  @cache.has_key?(m.cache_key).should be_false
207
- $sqls.last.should == "DELETE FROM items WHERE (id = 1)"
187
+ @c.db.sqls.should == ["SELECT * FROM items WHERE id = 1", "DELETE FROM items WHERE (id = 1)"]
208
188
  end
209
189
 
210
190
  it "should support #[] as a shortcut to #find with hash" do
211
191
  m = @c[:id => 3]
212
192
  @cache[m.cache_key].should be_nil
213
- $sqls.last.should == "SELECT * FROM items WHERE (id = 3) LIMIT 1"
193
+ @c.db.sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1"]
214
194
  m = @c[1]
215
195
  @cache[m.cache_key].should == m
216
- $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
217
- "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
196
+ @c.db.sqls.should == ["SELECT * FROM items WHERE id = 1"]
218
197
  @c[:id => 4]
219
- $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
220
- "SELECT * FROM items WHERE (id = 1) LIMIT 1", \
221
- "SELECT * FROM items WHERE (id = 4) LIMIT 1"]
198
+ @c.db.sqls.should == ["SELECT * FROM items WHERE (id = 4) LIMIT 1"]
222
199
 
223
- $sqls.clear
224
200
  m = @c2[:id => 3]
225
201
  @cache[m.cache_key].should be_nil
226
- $sqls.last.should == "SELECT * FROM items WHERE (id = 3) LIMIT 1"
202
+ @c.db.sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1"]
227
203
  m = @c2[1]
228
204
  @cache[m.cache_key].should == m
229
- $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
230
- "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
205
+ @c.db.sqls.should == ["SELECT * FROM items WHERE id = 1"]
231
206
  @c2[:id => 4]
232
- $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
233
- "SELECT * FROM items WHERE (id = 1) LIMIT 1", \
234
- "SELECT * FROM items WHERE (id = 4) LIMIT 1"]
207
+ @c.db.sqls.should == ["SELECT * FROM items WHERE (id = 4) LIMIT 1"]
235
208
  end
236
209
 
237
210
  it "should support ignore_exception option" do
@@ -245,6 +218,6 @@ describe Sequel::Model, "caching" do
245
218
  end
246
219
 
247
220
  it "should rescue an exception if cache_store is memcached and ignore_exception is enabled" do
248
- @c4[1].values.should == $cache_dataset_row
221
+ @c4[1].values.should == {:name => 'sharon', :id => 1}
249
222
  end
250
223
  end
@@ -2,17 +2,16 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe "class_table_inheritance plugin" do
4
4
  before do
5
- @db = db = MODEL_DB.clone
6
- def db.schema(table, opts={})
5
+ @db = Sequel.mock(:autoid=>proc{|sql| 1})
6
+ def @db.schema(table, opts={})
7
7
  {:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
8
8
  :managers=>[[:id, {:type=>:integer}], [:num_staff, {:type=>:integer}]],
9
9
  :executives=>[[:id, {:type=>:integer}], [:num_managers, {:type=>:integer}]],
10
10
  :staff=>[[:id, {:type=>:integer}], [:manager_id, {:type=>:integer}]],
11
- }[table]
11
+ }[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
12
12
  end
13
- def db.dataset(*args)
14
- ds = super(*args)
15
- def ds.columns
13
+ @db.extend_datasets do
14
+ def columns
16
15
  {[:employees]=>[:id, :name, :kind],
17
16
  [:managers]=>[:id, :num_staff],
18
17
  [:executives]=>[:id, :num_managers],
@@ -22,13 +21,8 @@ describe "class_table_inheritance plugin" do
22
21
  [:employees, :staff]=>[:id, :name, :kind, :manager_id],
23
22
  }[opts[:from] + (opts[:join] || []).map{|x| x.table}]
24
23
  end
25
- def ds.insert(*args)
26
- db << insert_sql(*args)
27
- 1
28
- end
29
- ds
30
24
  end
31
- class ::Employee < Sequel::Model(db)
25
+ class ::Employee < Sequel::Model(@db)
32
26
  def _refresh(x); @values[:id] = 1 end
33
27
  def self.columns
34
28
  dataset.columns
@@ -44,7 +38,7 @@ describe "class_table_inheritance plugin" do
44
38
  many_to_one :manager
45
39
  end
46
40
  @ds = Employee.dataset
47
- @db.reset
41
+ @db.sqls
48
42
  end
49
43
  after do
50
44
  Object.send(:remove_const, :Executive)
@@ -53,8 +47,8 @@ describe "class_table_inheritance plugin" do
53
47
  Object.send(:remove_const, :Employee)
54
48
  end
55
49
 
56
- specify "should have simple_table = nil" do
57
- Employee.simple_table.should == nil
50
+ specify "should have simple_table = nil for subclasses" do
51
+ Employee.simple_table.should == "employees"
58
52
  Manager.simple_table.should == nil
59
53
  Executive.simple_table.should == nil
60
54
  Staff.simple_table.should == nil
@@ -68,33 +62,18 @@ describe "class_table_inheritance plugin" do
68
62
  end
69
63
 
70
64
  it "should return rows with the correct class based on the polymorphic_key value" do
71
- def @ds.fetch_rows(sql)
72
- yield({:kind=>'Employee'})
73
- yield({:kind=>'Manager'})
74
- yield({:kind=>'Executive'})
75
- yield({:kind=>'Staff'})
76
- end
65
+ @ds._fetch = [{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Staff'}]
77
66
  Employee.all.collect{|x| x.class}.should == [Employee, Manager, Executive, Staff]
78
67
  end
79
68
 
80
69
  it "should return rows with the correct class based on the polymorphic_key value for subclasses" do
81
- ds = Manager.dataset
82
- def ds.fetch_rows(sql)
83
- yield({:kind=>'Manager'})
84
- yield({:kind=>'Executive'})
85
- end
70
+ Manager.dataset._fetch = [{:kind=>'Manager'}, {:kind=>'Executive'}]
86
71
  Manager.all.collect{|x| x.class}.should == [Manager, Executive]
87
72
  end
88
73
 
89
74
  it "should return rows with the current class if cti_key is nil" do
90
75
  Employee.plugin(:class_table_inheritance)
91
- ds = Employee.dataset
92
- def ds.fetch_rows(sql)
93
- yield({:kind=>'Employee'})
94
- yield({:kind=>'Manager'})
95
- yield({:kind=>'Executive'})
96
- yield({:kind=>'Staff'})
97
- end
76
+ @ds._fetch = [{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Staff'}]
98
77
  Employee.all.collect{|x| x.class}.should == [Employee, Employee, Employee, Employee]
99
78
  end
100
79
 
@@ -102,35 +81,19 @@ describe "class_table_inheritance plugin" do
102
81
  Employee.plugin(:class_table_inheritance)
103
82
  Object.send(:remove_const, :Executive)
104
83
  Object.send(:remove_const, :Manager)
105
- class ::Manager < Employee
106
- end
107
- class ::Executive < Manager
108
- end
109
- ds = Manager.dataset
110
- def ds.fetch_rows(sql)
111
- yield({:kind=>'Manager'})
112
- yield({:kind=>'Executive'})
113
- end
84
+ class ::Manager < Employee; end
85
+ class ::Executive < Manager; end
86
+ Manager.dataset._fetch = [{:kind=>'Manager'}, {:kind=>'Executive'}]
114
87
  Manager.all.collect{|x| x.class}.should == [Manager, Manager]
115
88
  end
116
89
 
117
90
  it "should fallback to the main class if the given class does not exist" do
118
- def @ds.fetch_rows(sql)
119
- yield({:kind=>'Employee'})
120
- yield({:kind=>'Manager'})
121
- yield({:kind=>'Blah'})
122
- yield({:kind=>'Staff'})
123
- end
91
+ @ds._fetch = [{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Blah'}, {:kind=>'Staff'}]
124
92
  Employee.all.collect{|x| x.class}.should == [Employee, Manager, Employee, Staff]
125
93
  end
126
94
 
127
95
  it "should fallback to the main class if the given class does not exist in subclasses" do
128
- ds = Manager.dataset
129
- def ds.fetch_rows(sql)
130
- yield({:kind=>'Manager'})
131
- yield({:kind=>'Executive'})
132
- yield({:kind=>'Blah'})
133
- end
96
+ Manager.dataset._fetch = [{:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Blah'}]
134
97
  Manager.all.collect{|x| x.class}.should == [Manager, Executive, Manager]
135
98
  end
136
99
 
@@ -167,19 +130,10 @@ describe "class_table_inheritance plugin" do
167
130
  end
168
131
 
169
132
  it "should lazily load attributes for columns in subclass tables" do
170
- ds = Manager.dataset
171
- def ds.fetch_rows(sql)
172
- @db << sql
173
- yield({:id=>1, :name=>'J', :kind=>'Executive', :num_staff=>2})
174
- end
133
+ Manager.dataset._fetch = {:id=>1, :name=>'J', :kind=>'Executive', :num_staff=>2}
175
134
  m = Manager[1]
176
135
  @db.sqls.should == ['SELECT * FROM employees INNER JOIN managers USING (id) WHERE (id = 1) LIMIT 1']
177
- @db.reset
178
- ds = Executive.dataset
179
- def ds.fetch_rows(sql)
180
- @db << sql
181
- yield({:num_managers=>3})
182
- end
136
+ Executive.dataset._fetch = {:num_managers=>3}
183
137
  m.num_managers.should == 3
184
138
  @db.sqls.should == ['SELECT num_managers FROM employees INNER JOIN managers USING (id) INNER JOIN executives USING (id) WHERE (id = 1) LIMIT 1']
185
139
  m.values.should == {:id=>1, :name=>'J', :kind=>'Executive', :num_staff=>2, :num_managers=>3}
@@ -210,20 +164,22 @@ describe "class_table_inheritance plugin" do
210
164
 
211
165
  it "should insert the correct rows into all tables when inserting" do
212
166
  Executive.create(:num_managers=>3, :num_staff=>2, :name=>'E')
213
- @db.sqls.length.should == 3
214
- @db.sqls[0].should =~ /INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Executive)', '(E|Executive)'\)/
215
- @db.sqls[1].should =~ /INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\)/
216
- @db.sqls[2].should =~ /INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\)/
167
+ sqls = @db.sqls
168
+ sqls.length.should == 3
169
+ sqls[0].should =~ /INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Executive)', '(E|Executive)'\)/
170
+ sqls[1].should =~ /INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\)/
171
+ sqls[2].should =~ /INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\)/
217
172
  end
218
173
 
219
174
  it "should insert the correct rows into all tables with a given primary key" do
220
175
  e = Executive.new(:num_managers=>3, :num_staff=>2, :name=>'E')
221
176
  e.id = 2
222
177
  e.save
223
- @db.sqls.length.should == 3
224
- @db.sqls[0].should =~ /INSERT INTO employees \((name|kind|id), (name|kind|id), (name|kind|id)\) VALUES \(('E'|'Executive'|2), ('E'|'Executive'|2), ('E'|'Executive'|2)\)/
225
- @db.sqls[1].should =~ /INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \(2, 2\)/
226
- @db.sqls[2].should =~ /INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([23], [23]\)/
178
+ sqls = @db.sqls
179
+ sqls.length.should == 3
180
+ sqls[0].should =~ /INSERT INTO employees \((name|kind|id), (name|kind|id), (name|kind|id)\) VALUES \(('E'|'Executive'|2), ('E'|'Executive'|2), ('E'|'Executive'|2)\)/
181
+ sqls[1].should =~ /INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \(2, 2\)/
182
+ sqls[2].should =~ /INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([23], [23]\)/
227
183
  end
228
184
 
229
185
  it "should update the correct rows in all tables when updating" do
@@ -232,21 +188,13 @@ describe "class_table_inheritance plugin" do
232
188
  end
233
189
 
234
190
  it "should handle many_to_one relationships correctly" do
235
- ds = Manager.dataset
236
- def ds.fetch_rows(sql)
237
- @db << sql
238
- yield({:id=>3, :name=>'E', :kind=>'Executive', :num_managers=>3})
239
- end
191
+ Manager.dataset._fetch = {:id=>3, :name=>'E', :kind=>'Executive', :num_managers=>3}
240
192
  Staff.load(:manager_id=>3).manager.should == Executive.load(:id=>3, :name=>'E', :kind=>'Executive', :num_managers=>3)
241
193
  @db.sqls.should == ['SELECT * FROM employees INNER JOIN managers USING (id) WHERE (managers.id = 3) LIMIT 1']
242
194
  end
243
195
 
244
196
  it "should handle one_to_many relationships correctly" do
245
- ds = Staff.dataset
246
- def ds.fetch_rows(sql)
247
- @db << sql
248
- yield({:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3})
249
- end
197
+ Staff.dataset._fetch = {:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3}
250
198
  Executive.load(:id=>3).staff_members.should == [Staff.load(:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3)]
251
199
  @db.sqls.should == ['SELECT * FROM employees INNER JOIN staff USING (id) WHERE (staff.manager_id = 3)']
252
200
  end
@@ -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