viking-sequel 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,3827 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ context "Dataset" do
4
+ before do
5
+ @dataset = Sequel::Dataset.new("db")
6
+ end
7
+
8
+ specify "should accept database and opts in initialize" do
9
+ db = "db"
10
+ opts = {:from => :test}
11
+ d = Sequel::Dataset.new(db, opts)
12
+ d.db.should be(db)
13
+ d.opts.should be(opts)
14
+
15
+ d = Sequel::Dataset.new(db)
16
+ d.db.should be(db)
17
+ d.opts.should be_a_kind_of(Hash)
18
+ d.opts.should == {}
19
+ end
20
+
21
+ specify "should provide clone for chainability" do
22
+ d1 = @dataset.clone(:from => [:test])
23
+ d1.class.should == @dataset.class
24
+ d1.should_not == @dataset
25
+ d1.db.should be(@dataset.db)
26
+ d1.opts[:from].should == [:test]
27
+ @dataset.opts[:from].should be_nil
28
+
29
+ d2 = d1.clone(:order => [:name])
30
+ d2.class.should == @dataset.class
31
+ d2.should_not == d1
32
+ d2.should_not == @dataset
33
+ d2.db.should be(@dataset.db)
34
+ d2.opts[:from].should == [:test]
35
+ d2.opts[:order].should == [:name]
36
+ d1.opts[:order].should be_nil
37
+ end
38
+
39
+ specify "should include Enumerable" do
40
+ Sequel::Dataset.included_modules.should include(Enumerable)
41
+ end
42
+
43
+ specify "should get quote_identifiers default from database" do
44
+ db = Sequel::Database.new(:quote_identifiers=>true)
45
+ db[:a].quote_identifiers?.should == true
46
+ db = Sequel::Database.new(:quote_identifiers=>false)
47
+ db[:a].quote_identifiers?.should == false
48
+ end
49
+
50
+ specify "should get identifier_input_method default from database" do
51
+ db = Sequel::Database.new(:identifier_input_method=>:upcase)
52
+ db[:a].identifier_input_method.should == :upcase
53
+ db = Sequel::Database.new(:identifier_input_method=>:downcase)
54
+ db[:a].identifier_input_method.should == :downcase
55
+ end
56
+
57
+ specify "should get identifier_output_method default from database" do
58
+ db = Sequel::Database.new(:identifier_output_method=>:upcase)
59
+ db[:a].identifier_output_method.should == :upcase
60
+ db = Sequel::Database.new(:identifier_output_method=>:downcase)
61
+ db[:a].identifier_output_method.should == :downcase
62
+ end
63
+ end
64
+
65
+ context "Dataset" do
66
+ before do
67
+ @dataset = Sequel::Dataset.new("db")
68
+ end
69
+
70
+ specify "should have quote_identifiers= method which changes literalization of identifiers" do
71
+ @dataset.quote_identifiers = true
72
+ @dataset.literal(:a).should == '"a"'
73
+ @dataset.quote_identifiers = false
74
+ @dataset.literal(:a).should == 'a'
75
+ end
76
+
77
+ specify "should have identifier_input_method= method which changes literalization of identifiers" do
78
+ @dataset.identifier_input_method = :upcase
79
+ @dataset.literal(:a).should == 'A'
80
+ @dataset.identifier_input_method = :downcase
81
+ @dataset.literal(:A).should == 'a'
82
+ @dataset.identifier_input_method = :reverse
83
+ @dataset.literal(:at_b).should == 'b_ta'
84
+ end
85
+
86
+ specify "should have identifier_output_method= method which changes identifiers returned from the database" do
87
+ @dataset.send(:output_identifier, "at_b_C").should == :at_b_C
88
+ @dataset.identifier_output_method = :upcase
89
+ @dataset.send(:output_identifier, "at_b_C").should == :AT_B_C
90
+ @dataset.identifier_output_method = :downcase
91
+ @dataset.send(:output_identifier, "at_b_C").should == :at_b_c
92
+ @dataset.identifier_output_method = :reverse
93
+ @dataset.send(:output_identifier, "at_b_C").should == :C_b_ta
94
+ end
95
+
96
+ specify "should have output_identifier handle empty identifiers" do
97
+ @dataset.send(:output_identifier, "").should == :untitled
98
+ @dataset.identifier_output_method = :upcase
99
+ @dataset.send(:output_identifier, "").should == :UNTITLED
100
+ @dataset.identifier_output_method = :downcase
101
+ @dataset.send(:output_identifier, "").should == :untitled
102
+ @dataset.identifier_output_method = :reverse
103
+ @dataset.send(:output_identifier, "").should == :deltitnu
104
+ end
105
+ end
106
+
107
+ context "Dataset#clone" do
108
+ before do
109
+ @dataset = Sequel::Dataset.new(nil).from(:items)
110
+ end
111
+
112
+ specify "should create an exact copy of the dataset" do
113
+ @dataset.row_proc = Proc.new{|r| r}
114
+ @clone = @dataset.clone
115
+
116
+ @clone.should_not === @dataset
117
+ @clone.class.should == @dataset.class
118
+ @clone.opts.should == @dataset.opts
119
+ @clone.row_proc.should == @dataset.row_proc
120
+ end
121
+
122
+ specify "should deep-copy the dataset opts" do
123
+ @clone = @dataset.clone
124
+
125
+ @clone.opts.should_not equal(@dataset.opts)
126
+ @dataset.filter!(:a => 'b')
127
+ @clone.opts[:filter].should be_nil
128
+ end
129
+
130
+ specify "should return a clone self" do
131
+ clone = @dataset.clone({})
132
+ clone.class.should == @dataset.class
133
+ clone.db.should == @dataset.db
134
+ clone.opts.should == @dataset.opts
135
+ end
136
+
137
+ specify "should merge the specified options" do
138
+ clone = @dataset.clone(1 => 2)
139
+ clone.opts.should == {1 => 2, :from => [:items]}
140
+ end
141
+
142
+ specify "should overwrite existing options" do
143
+ clone = @dataset.clone(:from => [:other])
144
+ clone.opts.should == {:from => [:other]}
145
+ end
146
+
147
+ specify "should create a clone with a deep copy of options" do
148
+ clone = @dataset.clone(:from => [:other])
149
+ @dataset.opts[:from].should == [:items]
150
+ clone.opts[:from].should == [:other]
151
+ end
152
+
153
+ specify "should return an object with the same modules included" do
154
+ m = Module.new do
155
+ def __xyz__; "xyz"; end
156
+ end
157
+ @dataset.extend(m)
158
+ @dataset.clone({}).should respond_to(:__xyz__)
159
+ end
160
+ end
161
+
162
+ context "A simple dataset" do
163
+ before do
164
+ @dataset = Sequel::Dataset.new(nil).from(:test)
165
+ end
166
+
167
+ specify "should format a select statement" do
168
+ @dataset.select_sql.should == 'SELECT * FROM test'
169
+ end
170
+
171
+ specify "should format a delete statement" do
172
+ @dataset.delete_sql.should == 'DELETE FROM test'
173
+ end
174
+
175
+ specify "should format a truncate statement" do
176
+ @dataset.truncate_sql.should == 'TRUNCATE TABLE test'
177
+ end
178
+
179
+ specify "should format an insert statement with default values" do
180
+ @dataset.insert_sql.should == 'INSERT INTO test DEFAULT VALUES'
181
+ end
182
+
183
+ specify "should format an insert statement with hash" do
184
+ @dataset.insert_sql(:name => 'wxyz', :price => 342).
185
+ should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
186
+
187
+ @dataset.insert_sql({}).should == "INSERT INTO test DEFAULT VALUES"
188
+ end
189
+
190
+ specify "should format an insert statement with string keys" do
191
+ @dataset.insert_sql('name' => 'wxyz', 'price' => 342).
192
+ should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
193
+ end
194
+
195
+ specify "should format an insert statement with an object that respond_to? :values" do
196
+ dbb = Sequel::Database.new
197
+
198
+ v = Object.new
199
+ def v.values; {:a => 1}; end
200
+
201
+ @dataset.insert_sql(v).should == "INSERT INTO test (a) VALUES (1)"
202
+
203
+ def v.values; {}; end
204
+ @dataset.insert_sql(v).should == "INSERT INTO test DEFAULT VALUES"
205
+ end
206
+
207
+ specify "should format an insert statement with an arbitrary value" do
208
+ @dataset.insert_sql(123).should == "INSERT INTO test VALUES (123)"
209
+ end
210
+
211
+ specify "should format an insert statement with sub-query" do
212
+ @sub = Sequel::Dataset.new(nil).from(:something).filter(:x => 2)
213
+ @dataset.insert_sql(@sub).should == \
214
+ "INSERT INTO test SELECT * FROM something WHERE (x = 2)"
215
+ end
216
+
217
+ specify "should format an insert statement with array" do
218
+ @dataset.insert_sql('a', 2, 6.5).should ==
219
+ "INSERT INTO test VALUES ('a', 2, 6.5)"
220
+ end
221
+
222
+ specify "should format an update statement" do
223
+ @dataset.update_sql(:name => 'abc').should ==
224
+ "UPDATE test SET name = 'abc'"
225
+ end
226
+
227
+ specify "should be able to return rows for arbitrary SQL" do
228
+ @dataset.clone(:sql => 'xxx yyy zzz').select_sql.should ==
229
+ "xxx yyy zzz"
230
+ end
231
+
232
+ specify "should use the :sql option for all sql methods" do
233
+ sql = "X"
234
+ ds = Sequel::Dataset.new(nil, :sql=>sql)
235
+ ds.sql.should == sql
236
+ ds.select_sql.should == sql
237
+ ds.insert_sql.should == sql
238
+ ds.delete_sql.should == sql
239
+ ds.update_sql.should == sql
240
+ ds.truncate_sql.should == sql
241
+ end
242
+ end
243
+
244
+ context "A dataset with multiple tables in its FROM clause" do
245
+ before do
246
+ @dataset = Sequel::Dataset.new(nil).from(:t1, :t2)
247
+ end
248
+
249
+ specify "should raise on #update_sql" do
250
+ proc {@dataset.update_sql(:a=>1)}.should raise_error(Sequel::InvalidOperation)
251
+ end
252
+
253
+ specify "should raise on #delete_sql" do
254
+ proc {@dataset.delete_sql}.should raise_error(Sequel::InvalidOperation)
255
+ end
256
+
257
+ specify "should raise on #truncate_sql" do
258
+ proc {@dataset.truncate_sql}.should raise_error(Sequel::InvalidOperation)
259
+ end
260
+
261
+ specify "should raise on #insert_sql" do
262
+ proc {@dataset.insert_sql}.should raise_error(Sequel::InvalidOperation)
263
+ end
264
+
265
+ specify "should generate a select query FROM all specified tables" do
266
+ @dataset.select_sql.should == "SELECT * FROM t1, t2"
267
+ end
268
+ end
269
+
270
+ context "Dataset#unused_table_alias" do
271
+ before do
272
+ @ds = Sequel::Dataset.new(nil).from(:test)
273
+ end
274
+
275
+ specify "should return given symbol if it hasn't already been used" do
276
+ @ds.unused_table_alias(:blah).should == :blah
277
+ end
278
+
279
+ specify "should return a symbol specifying an alias that hasn't already been used if it has already been used" do
280
+ @ds.unused_table_alias(:test).should == :test_0
281
+ @ds.from(:test, :test_0).unused_table_alias(:test).should == :test_1
282
+ @ds.from(:test, :test_0).cross_join(:test_1).unused_table_alias(:test).should == :test_2
283
+ end
284
+
285
+ specify "should return an appropriate symbol if given other forms of identifiers" do
286
+ @ds.unused_table_alias('test').should == :test_0
287
+ @ds.unused_table_alias(:b__t___test).should == :test_0
288
+ @ds.unused_table_alias(:b__test).should == :test_0
289
+ @ds.unused_table_alias(:test.qualify(:b)).should == :test_0
290
+ @ds.unused_table_alias(:b.as(:test)).should == :test_0
291
+ @ds.unused_table_alias(:b.as(:test.identifier)).should == :test_0
292
+ @ds.unused_table_alias(:b.as('test')).should == :test_0
293
+ @ds.unused_table_alias(:test.identifier).should == :test_0
294
+ end
295
+ end
296
+
297
+ context "Dataset#exists" do
298
+ before do
299
+ @ds1 = Sequel::Dataset.new(nil).from(:test)
300
+ @ds2 = @ds1.filter(:price.sql_number < 100)
301
+ @ds3 = @ds1.filter(:price.sql_number > 50)
302
+ end
303
+
304
+ specify "should work in filters" do
305
+ @ds1.filter(@ds2.exists).sql.should ==
306
+ 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
307
+ @ds1.filter(@ds2.exists & @ds3.exists).sql.should ==
308
+ 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)) AND EXISTS (SELECT * FROM test WHERE (price > 50)))'
309
+ end
310
+
311
+ specify "should work in select" do
312
+ @ds1.select(@ds2.exists.as(:a), @ds3.exists.as(:b)).sql.should ==
313
+ 'SELECT EXISTS (SELECT * FROM test WHERE (price < 100)) AS a, EXISTS (SELECT * FROM test WHERE (price > 50)) AS b FROM test'
314
+ end
315
+ end
316
+
317
+ context "Dataset#where" do
318
+ before do
319
+ @dataset = Sequel::Dataset.new(nil).from(:test)
320
+ @d1 = @dataset.where(:region => 'Asia')
321
+ @d2 = @dataset.where('region = ?', 'Asia')
322
+ @d3 = @dataset.where("a = 1")
323
+ end
324
+
325
+ specify "should just clone if given an empty argument" do
326
+ @dataset.where({}).sql.should == @dataset.sql
327
+ @dataset.where([]).sql.should == @dataset.sql
328
+ @dataset.where('').sql.should == @dataset.sql
329
+
330
+ @dataset.filter({}).sql.should == @dataset.sql
331
+ @dataset.filter([]).sql.should == @dataset.sql
332
+ @dataset.filter('').sql.should == @dataset.sql
333
+ end
334
+
335
+ specify "should work with hashes" do
336
+ @dataset.where(:name => 'xyz', :price => 342).select_sql.
337
+ should match(/WHERE \(\(name = 'xyz'\) AND \(price = 342\)\)|WHERE \(\(price = 342\) AND \(name = 'xyz'\)\)/)
338
+ end
339
+
340
+ specify "should work with a string with placeholders and arguments for those placeholders" do
341
+ @dataset.where('price < ? AND id in ?', 100, [1, 2, 3]).select_sql.should ==
342
+ "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
343
+ end
344
+
345
+ specify "should not modify passed array with placeholders" do
346
+ a = ['price < ? AND id in ?', 100, 1, 2, 3]
347
+ b = a.dup
348
+ @dataset.where(a)
349
+ b.should == a
350
+ end
351
+
352
+ specify "should work with strings (custom SQL expressions)" do
353
+ @dataset.where('(a = 1 AND b = 2)').select_sql.should ==
354
+ "SELECT * FROM test WHERE ((a = 1 AND b = 2))"
355
+ end
356
+
357
+ specify "should work with a string with named placeholders and a hash of placeholder value arguments" do
358
+ @dataset.where('price < :price AND id in :ids', :price=>100, :ids=>[1, 2, 3]).select_sql.should ==
359
+ "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
360
+ end
361
+
362
+ specify "should not modify passed array with named placeholders" do
363
+ a = ['price < :price AND id in :ids', {:price=>100}]
364
+ b = a.dup
365
+ @dataset.where(a)
366
+ b.should == a
367
+ end
368
+
369
+ specify "should not replace named placeholders that don't existin in the hash" do
370
+ @dataset.where('price < :price AND id in :ids', :price=>100).select_sql.should ==
371
+ "SELECT * FROM test WHERE (price < 100 AND id in :ids)"
372
+ end
373
+
374
+ specify "should handle partial names" do
375
+ @dataset.where('price < :price AND id = :p', :p=>2, :price=>100).select_sql.should ==
376
+ "SELECT * FROM test WHERE (price < 100 AND id = 2)"
377
+ end
378
+
379
+ specify "should affect select, delete and update statements" do
380
+ @d1.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
381
+ @d1.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
382
+ @d1.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
383
+
384
+ @d2.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
385
+ @d2.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
386
+ @d2.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
387
+
388
+ @d3.select_sql.should == "SELECT * FROM test WHERE (a = 1)"
389
+ @d3.delete_sql.should == "DELETE FROM test WHERE (a = 1)"
390
+ @d3.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (a = 1)"
391
+
392
+ end
393
+
394
+ specify "should be composable using AND operator (for scoping)" do
395
+ # hashes are merged, no problem
396
+ @d1.where(:size => 'big').select_sql.should ==
397
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (size = 'big'))"
398
+
399
+ # hash and string
400
+ @d1.where('population > 1000').select_sql.should ==
401
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (population > 1000))"
402
+ @d1.where('(a > 1) OR (b < 2)').select_sql.should ==
403
+ "SELECT * FROM test WHERE ((region = 'Asia') AND ((a > 1) OR (b < 2)))"
404
+
405
+ # hash and array
406
+ @d1.where('GDP > ?', 1000).select_sql.should ==
407
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > 1000))"
408
+
409
+ # array and array
410
+ @d2.where('GDP > ?', 1000).select_sql.should ==
411
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > 1000))"
412
+
413
+ # array and hash
414
+ @d2.where(:name => ['Japan', 'China']).select_sql.should ==
415
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (name IN ('Japan', 'China')))"
416
+
417
+ # array and string
418
+ @d2.where('GDP > ?').select_sql.should ==
419
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > ?))"
420
+
421
+ # string and string
422
+ @d3.where('b = 2').select_sql.should ==
423
+ "SELECT * FROM test WHERE ((a = 1) AND (b = 2))"
424
+
425
+ # string and hash
426
+ @d3.where(:c => 3).select_sql.should ==
427
+ "SELECT * FROM test WHERE ((a = 1) AND (c = 3))"
428
+
429
+ # string and array
430
+ @d3.where('d = ?', 4).select_sql.should ==
431
+ "SELECT * FROM test WHERE ((a = 1) AND (d = 4))"
432
+ end
433
+
434
+ specify "should be composable using AND operator (for scoping) with block" do
435
+ @d3.where{:e.sql_number < 5}.select_sql.should ==
436
+ "SELECT * FROM test WHERE ((a = 1) AND (e < 5))"
437
+ end
438
+
439
+ specify "should accept ranges" do
440
+ @dataset.filter(:id => 4..7).sql.should ==
441
+ 'SELECT * FROM test WHERE ((id >= 4) AND (id <= 7))'
442
+ @dataset.filter(:id => 4...7).sql.should ==
443
+ 'SELECT * FROM test WHERE ((id >= 4) AND (id < 7))'
444
+
445
+ @dataset.filter(:table__id => 4..7).sql.should ==
446
+ 'SELECT * FROM test WHERE ((table.id >= 4) AND (table.id <= 7))'
447
+ @dataset.filter(:table__id => 4...7).sql.should ==
448
+ 'SELECT * FROM test WHERE ((table.id >= 4) AND (table.id < 7))'
449
+ end
450
+
451
+ specify "should accept nil" do
452
+ @dataset.filter(:owner_id => nil).sql.should ==
453
+ 'SELECT * FROM test WHERE (owner_id IS NULL)'
454
+ end
455
+
456
+ specify "should accept a subquery" do
457
+ @dataset.filter('gdp > ?', @d1.select(:avg.sql_function(:gdp))).sql.should ==
458
+ "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
459
+ end
460
+
461
+ specify "should handle all types of IN/NOT IN queries" do
462
+ @dataset.filter(:id => @d1.select(:id)).sql.should == "SELECT * FROM test WHERE (id IN (SELECT id FROM test WHERE (region = 'Asia')))"
463
+ @dataset.filter(:id => []).sql.should == "SELECT * FROM test WHERE (id != id)"
464
+ @dataset.filter(:id => [1, 2]).sql.should == "SELECT * FROM test WHERE (id IN (1, 2))"
465
+ @dataset.filter([:id1, :id2] => @d1.select(:id1, :id2)).sql.should == "SELECT * FROM test WHERE ((id1, id2) IN (SELECT id1, id2 FROM test WHERE (region = 'Asia')))"
466
+ @dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
467
+ @dataset.filter([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE ((id1, id2) IN ((1, 2), (3, 4)))"
468
+
469
+ @dataset.exclude(:id => @d1.select(:id)).sql.should == "SELECT * FROM test WHERE (id NOT IN (SELECT id FROM test WHERE (region = 'Asia')))"
470
+ @dataset.exclude(:id => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
471
+ @dataset.exclude(:id => [1, 2]).sql.should == "SELECT * FROM test WHERE (id NOT IN (1, 2))"
472
+ @dataset.exclude([:id1, :id2] => @d1.select(:id1, :id2)).sql.should == "SELECT * FROM test WHERE ((id1, id2) NOT IN (SELECT id1, id2 FROM test WHERE (region = 'Asia')))"
473
+ @dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
474
+ @dataset.exclude([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE ((id1, id2) NOT IN ((1, 2), (3, 4)))"
475
+ end
476
+
477
+ specify "should handle IN/NOT IN queries with multiple columns and an array where the database doesn't support it" do
478
+ @dataset.meta_def(:supports_multiple_column_in?){false}
479
+ @dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
480
+ @dataset.filter([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
481
+ @dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
482
+ @dataset.exclude([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
483
+ end
484
+
485
+ specify "should handle IN/NOT IN queries with multiple columns and a dataset where the database doesn't support it" do
486
+ @dataset.meta_def(:supports_multiple_column_in?){false}
487
+ d1 = @d1.select(:id1, :id2)
488
+ def d1.fetch_rows(sql)
489
+ @sql_used = sql
490
+ @columns = [:id1, :id2]
491
+ yield(:id1=>1, :id2=>2)
492
+ yield(:id1=>3, :id2=>4)
493
+ end
494
+ d1.instance_variable_get(:@sql_used).should == nil
495
+ @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
496
+ d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
497
+ d1.instance_variable_set(:@sql_used, nil)
498
+ @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
499
+ d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
500
+ end
501
+
502
+ specify "should handle IN/NOT IN queries with multiple columns and an empty dataset where the database doesn't support it" do
503
+ @dataset.meta_def(:supports_multiple_column_in?){false}
504
+ d1 = @d1.select(:id1, :id2)
505
+ def d1.fetch_rows(sql)
506
+ @sql_used = sql
507
+ @columns = [:id1, :id2]
508
+ end
509
+ d1.instance_variable_get(:@sql_used).should == nil
510
+ @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
511
+ d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
512
+ d1.instance_variable_set(:@sql_used, nil)
513
+ @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (1 = 1)"
514
+ d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
515
+ end
516
+
517
+ specify "should accept a subquery for an EXISTS clause" do
518
+ a = @dataset.filter(:price.sql_number < 100)
519
+ @dataset.filter(a.exists).sql.should ==
520
+ 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
521
+ end
522
+
523
+ specify "should accept proc expressions" do
524
+ d = @d1.select(:avg.sql_function(:gdp))
525
+ @dataset.filter {:gdp.sql_number > d}.sql.should ==
526
+ "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
527
+
528
+ @dataset.filter {:a.sql_number < 1}.sql.should ==
529
+ 'SELECT * FROM test WHERE (a < 1)'
530
+
531
+ @dataset.filter {(:a.sql_number >= 1) & (:b.sql_number <= 2)}.sql.should ==
532
+ 'SELECT * FROM test WHERE ((a >= 1) AND (b <= 2))'
533
+
534
+ @dataset.filter {:c.like 'ABC%'}.sql.should ==
535
+ "SELECT * FROM test WHERE (c LIKE 'ABC%')"
536
+
537
+ @dataset.filter {:c.like 'ABC%'}.sql.should ==
538
+ "SELECT * FROM test WHERE (c LIKE 'ABC%')"
539
+
540
+ @dataset.filter {:c.like 'ABC%', '%XYZ'}.sql.should ==
541
+ "SELECT * FROM test WHERE ((c LIKE 'ABC%') OR (c LIKE '%XYZ'))"
542
+ end
543
+
544
+ specify "should work for grouped datasets" do
545
+ @dataset.group(:a).filter(:b => 1).sql.should ==
546
+ 'SELECT * FROM test WHERE (b = 1) GROUP BY a'
547
+ end
548
+
549
+ specify "should accept true and false as arguments" do
550
+ @dataset.filter(true).sql.should ==
551
+ "SELECT * FROM test WHERE 't'"
552
+ @dataset.filter(false).sql.should ==
553
+ "SELECT * FROM test WHERE 'f'"
554
+ end
555
+
556
+ specify "should allow the use of multiple arguments" do
557
+ @dataset.filter(:a, :b).sql.should ==
558
+ 'SELECT * FROM test WHERE (a AND b)'
559
+ @dataset.filter(:a, :b=>1).sql.should ==
560
+ 'SELECT * FROM test WHERE (a AND (b = 1))'
561
+ @dataset.filter(:a, :c.sql_number > 3, :b=>1).sql.should ==
562
+ 'SELECT * FROM test WHERE (a AND (c > 3) AND (b = 1))'
563
+ end
564
+
565
+ specify "should allow the use of blocks and arguments simultaneously" do
566
+ @dataset.filter(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should ==
567
+ 'SELECT * FROM test WHERE ((zz < 3) AND (yy > 3))'
568
+ end
569
+
570
+ specify "should yield a VirtualRow to the block" do
571
+ x = nil
572
+ @dataset.filter{|r| x = r; false}
573
+ x.should be_a_kind_of(Sequel::SQL::VirtualRow)
574
+ @dataset.filter{|r| ((r.name < 'b') & {r.table__id => 1}) | r.is_active(r.blah, r.xx, r.x__y_z)}.sql.should ==
575
+ "SELECT * FROM test WHERE (((name < 'b') AND (table.id = 1)) OR is_active(blah, xx, x.y_z))"
576
+ end
577
+
578
+ specify "should instance_eval the block in the context of a VirtualRow if the block doesn't request an argument" do
579
+ x = nil
580
+ @dataset.filter{x = self; false}
581
+ x.should be_a_kind_of(Sequel::SQL::VirtualRow)
582
+ @dataset.filter{((name < 'b') & {table__id => 1}) | is_active(blah, xx, x__y_z)}.sql.should ==
583
+ "SELECT * FROM test WHERE (((name < 'b') AND (table.id = 1)) OR is_active(blah, xx, x.y_z))"
584
+ end
585
+
586
+ specify "should raise an error if an invalid argument is used" do
587
+ proc{@dataset.filter(1)}.should raise_error(Sequel::Error)
588
+ end
589
+
590
+ specify "should raise an error if a NumericExpression or StringExpression is used" do
591
+ proc{@dataset.filter(:x + 1)}.should raise_error(Sequel::Error)
592
+ proc{@dataset.filter(:x.sql_string)}.should raise_error(Sequel::Error)
593
+ end
594
+ end
595
+
596
+ context "Dataset#or" do
597
+ before do
598
+ @dataset = Sequel::Dataset.new(nil).from(:test)
599
+ @d1 = @dataset.where(:x => 1)
600
+ end
601
+
602
+ specify "should raise if no filter exists" do
603
+ proc {@dataset.or(:a => 1)}.should raise_error(Sequel::Error)
604
+ end
605
+
606
+ specify "should add an alternative expression to the where clause" do
607
+ @d1.or(:y => 2).sql.should ==
608
+ 'SELECT * FROM test WHERE ((x = 1) OR (y = 2))'
609
+ end
610
+
611
+ specify "should accept all forms of filters" do
612
+ @d1.or('y > ?', 2).sql.should ==
613
+ 'SELECT * FROM test WHERE ((x = 1) OR (y > 2))'
614
+ @d1.or(:yy.sql_number > 3).sql.should ==
615
+ 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
616
+ end
617
+
618
+ specify "should accept blocks passed to filter" do
619
+ @d1.or{:yy.sql_number > 3}.sql.should ==
620
+ 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
621
+ end
622
+
623
+ specify "should correctly add parens to give predictable results" do
624
+ @d1.filter(:y => 2).or(:z => 3).sql.should ==
625
+ 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
626
+
627
+ @d1.or(:y => 2).filter(:z => 3).sql.should ==
628
+ 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
629
+ end
630
+
631
+ specify "should allow the use of blocks and arguments simultaneously" do
632
+ @d1.or(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should ==
633
+ 'SELECT * FROM test WHERE ((x = 1) OR ((zz < 3) AND (yy > 3)))'
634
+ end
635
+ end
636
+
637
+ context "Dataset#and" do
638
+ before do
639
+ @dataset = Sequel::Dataset.new(nil).from(:test)
640
+ @d1 = @dataset.where(:x => 1)
641
+ end
642
+
643
+ specify "should raise if no filter exists" do
644
+ proc {@dataset.and(:a => 1)}.should raise_error(Sequel::Error)
645
+ proc {@dataset.where(:a => 1).group(:t).and(:b => 2)}.should_not raise_error(Sequel::Error)
646
+ @dataset.where(:a => 1).group(:t).and(:b => 2).sql ==
647
+ "SELECT * FROM test WHERE (a = 1) AND (b = 2) GROUP BY t"
648
+ end
649
+
650
+ specify "should add an alternative expression to the where clause" do
651
+ @d1.and(:y => 2).sql.should ==
652
+ 'SELECT * FROM test WHERE ((x = 1) AND (y = 2))'
653
+ end
654
+
655
+ specify "should accept all forms of filters" do
656
+ # probably not exhaustive, but good enough
657
+ @d1.and('y > ?', 2).sql.should ==
658
+ 'SELECT * FROM test WHERE ((x = 1) AND (y > 2))'
659
+ @d1.and(:yy.sql_number > 3).sql.should ==
660
+ 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
661
+ end
662
+
663
+ specify "should accept blocks passed to filter" do
664
+ @d1.and {:yy.sql_number > 3}.sql.should ==
665
+ 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
666
+ end
667
+
668
+ specify "should correctly add parens to give predictable results" do
669
+ @d1.or(:y => 2).and(:z => 3).sql.should ==
670
+ 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
671
+
672
+ @d1.and(:y => 2).or(:z => 3).sql.should ==
673
+ 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
674
+ end
675
+ end
676
+
677
+ context "Dataset#exclude" do
678
+ before do
679
+ @dataset = Sequel::Dataset.new(nil).from(:test)
680
+ end
681
+
682
+ specify "should correctly negate the expression when one condition is given" do
683
+ @dataset.exclude(:region=>'Asia').select_sql.should ==
684
+ "SELECT * FROM test WHERE (region != 'Asia')"
685
+ end
686
+
687
+ specify "should take multiple conditions as a hash and express the logic correctly in SQL" do
688
+ @dataset.exclude(:region => 'Asia', :name => 'Japan').select_sql.
689
+ should match(Regexp.union(/WHERE \(\(region != 'Asia'\) OR \(name != 'Japan'\)\)/,
690
+ /WHERE \(\(name != 'Japan'\) OR \(region != 'Asia'\)\)/))
691
+ end
692
+
693
+ specify "should parenthesize a single string condition correctly" do
694
+ @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should ==
695
+ "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
696
+ end
697
+
698
+ specify "should parenthesize an array condition correctly" do
699
+ @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should ==
700
+ "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
701
+ end
702
+
703
+ specify "should correctly parenthesize when it is used twice" do
704
+ @dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should ==
705
+ "SELECT * FROM test WHERE ((region != 'Asia') AND (name != 'Japan'))"
706
+ end
707
+
708
+ specify "should support proc expressions" do
709
+ @dataset.exclude{:id.sql_number < 6}.sql.should ==
710
+ 'SELECT * FROM test WHERE (id >= 6)'
711
+ end
712
+
713
+ specify "should allow the use of blocks and arguments simultaneously" do
714
+ @dataset.exclude(:id => (7..11)){:id.sql_number < 6}.sql.should ==
715
+ 'SELECT * FROM test WHERE ((id < 7) OR (id > 11) OR (id >= 6))'
716
+ @dataset.exclude([:id, 1], [:x, 3]){:id.sql_number < 6}.sql.should ==
717
+ 'SELECT * FROM test WHERE ((id != 1) OR (x != 3) OR (id >= 6))'
718
+ end
719
+ end
720
+
721
+ context "Dataset#invert" do
722
+ before do
723
+ @d = Sequel::Dataset.new(nil).from(:test)
724
+ end
725
+
726
+ specify "should raise error if the dataset is not filtered" do
727
+ proc{@d.invert}.should raise_error(Sequel::Error)
728
+ end
729
+
730
+ specify "should invert current filter if dataset is filtered" do
731
+ @d.filter(:x).invert.sql.should == 'SELECT * FROM test WHERE NOT x'
732
+ end
733
+
734
+ specify "should invert both having and where if both are preset" do
735
+ @d.filter(:x).group(:x).having(:x).invert.sql.should == 'SELECT * FROM test WHERE NOT x GROUP BY x HAVING NOT x'
736
+ end
737
+ end
738
+
739
+ context "Dataset#having" do
740
+ before do
741
+ @dataset = Sequel::Dataset.new(nil).from(:test)
742
+ @grouped = @dataset.group(:region).select(:region, :sum.sql_function(:population), :avg.sql_function(:gdp))
743
+ @d1 = @grouped.having('sum(population) > 10')
744
+ @d2 = @grouped.having(:region => 'Asia')
745
+ @columns = "region, sum(population), avg(gdp)"
746
+ end
747
+
748
+ specify "should just clone if given an empty argument" do
749
+ @dataset.having({}).sql.should == @dataset.sql
750
+ @dataset.having([]).sql.should == @dataset.sql
751
+ @dataset.having('').sql.should == @dataset.sql
752
+ end
753
+
754
+ specify "should affect select statements" do
755
+ @d1.select_sql.should ==
756
+ "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
757
+ end
758
+
759
+ specify "should support proc expressions" do
760
+ @grouped.having {:sum.sql_function(:population) > 10}.sql.should ==
761
+ "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
762
+ end
763
+
764
+ specify "should work with and on the having clause" do
765
+ @grouped.having( :a.sql_number > 1 ).and( :b.sql_number < 2 ).sql.should ==
766
+ "SELECT #{@columns} FROM test GROUP BY region HAVING ((a > 1) AND (b < 2))"
767
+ end
768
+ end
769
+
770
+ context "a grouped dataset" do
771
+ before do
772
+ @dataset = Sequel::Dataset.new(nil).from(:test).group(:type_id)
773
+ end
774
+
775
+ specify "should raise when trying to generate an update statement" do
776
+ proc {@dataset.update_sql(:id => 0)}.should raise_error
777
+ end
778
+
779
+ specify "should raise when trying to generate a delete statement" do
780
+ proc {@dataset.delete_sql}.should raise_error
781
+ end
782
+
783
+ specify "should raise when trying to generate a truncate statement" do
784
+ proc {@dataset.truncate_sql}.should raise_error
785
+ end
786
+
787
+ specify "should raise when trying to generate an insert statement" do
788
+ proc {@dataset.insert_sql}.should raise_error
789
+ end
790
+
791
+ specify "should specify the grouping in generated select statement" do
792
+ @dataset.select_sql.should ==
793
+ "SELECT * FROM test GROUP BY type_id"
794
+ end
795
+
796
+ specify "should format the right statement for counting (as a subquery)" do
797
+ db = MockDatabase.new
798
+ db[:test].select(:name).group(:name).count
799
+ db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT name FROM test GROUP BY name) AS t1 LIMIT 1"]
800
+ end
801
+ end
802
+
803
+ context "Dataset#group_by" do
804
+ before do
805
+ @dataset = Sequel::Dataset.new(nil).from(:test).group_by(:type_id)
806
+ end
807
+
808
+ specify "should raise when trying to generate an update statement" do
809
+ proc {@dataset.update_sql(:id => 0)}.should raise_error
810
+ end
811
+
812
+ specify "should raise when trying to generate a delete statement" do
813
+ proc {@dataset.delete_sql}.should raise_error
814
+ end
815
+
816
+ specify "should specify the grouping in generated select statement" do
817
+ @dataset.select_sql.should ==
818
+ "SELECT * FROM test GROUP BY type_id"
819
+ @dataset.group_by(:a, :b).select_sql.should ==
820
+ "SELECT * FROM test GROUP BY a, b"
821
+ @dataset.group_by(:type_id=>nil).select_sql.should ==
822
+ "SELECT * FROM test GROUP BY (type_id IS NULL)"
823
+ end
824
+
825
+ specify "should ungroup when passed nil, empty, or no arguments" do
826
+ @dataset.group_by.select_sql.should ==
827
+ "SELECT * FROM test"
828
+ @dataset.group_by(nil).select_sql.should ==
829
+ "SELECT * FROM test"
830
+ end
831
+
832
+ specify "should undo previous grouping" do
833
+ @dataset.group_by(:a).group_by(:b).select_sql.should ==
834
+ "SELECT * FROM test GROUP BY b"
835
+ @dataset.group_by(:a, :b).group_by.select_sql.should ==
836
+ "SELECT * FROM test"
837
+ end
838
+
839
+ specify "should be aliased as #group" do
840
+ @dataset.group(:type_id=>nil).select_sql.should ==
841
+ "SELECT * FROM test GROUP BY (type_id IS NULL)"
842
+ end
843
+ end
844
+
845
+ context "Dataset#as" do
846
+ specify "should set up an alias" do
847
+ dataset = Sequel::Dataset.new(nil).from(:test)
848
+ dataset.select(dataset.limit(1).select(:name).as(:n)).sql.should == \
849
+ 'SELECT (SELECT name FROM test LIMIT 1) AS n FROM test'
850
+ end
851
+ end
852
+
853
+ context "Dataset#literal" do
854
+ before do
855
+ @dataset = Sequel::Dataset.new(nil).from(:test)
856
+ end
857
+
858
+ specify "should escape strings properly" do
859
+ @dataset.literal('abc').should == "'abc'"
860
+ @dataset.literal('a"x"bc').should == "'a\"x\"bc'"
861
+ @dataset.literal("a'bc").should == "'a''bc'"
862
+ @dataset.literal("a''bc").should == "'a''''bc'"
863
+ @dataset.literal("a\\bc").should == "'a\\\\bc'"
864
+ @dataset.literal("a\\\\bc").should == "'a\\\\\\\\bc'"
865
+ @dataset.literal("a\\'bc").should == "'a\\\\''bc'"
866
+ end
867
+
868
+ specify "should escape blobs as strings by default" do
869
+ @dataset.literal('abc'.to_sequel_blob).should == "'abc'"
870
+ end
871
+
872
+ specify "should literalize numbers properly" do
873
+ @dataset.literal(1).should == "1"
874
+ @dataset.literal(1.5).should == "1.5"
875
+ end
876
+
877
+ specify "should literalize nil as NULL" do
878
+ @dataset.literal(nil).should == "NULL"
879
+ end
880
+
881
+ specify "should literalize an array properly" do
882
+ @dataset.literal([]).should == "(NULL)"
883
+ @dataset.literal([1, 'abc', 3]).should == "(1, 'abc', 3)"
884
+ @dataset.literal([1, "a'b''c", 3]).should == "(1, 'a''b''''c', 3)"
885
+ end
886
+
887
+ specify "should literalize symbols as column references" do
888
+ @dataset.literal(:name).should == "name"
889
+ @dataset.literal(:items__name).should == "items.name"
890
+ end
891
+
892
+ specify "should call sql_literal with dataset on type if not natively supported and the object responds to it" do
893
+ @a = Class.new do
894
+ def sql_literal(ds)
895
+ "called #{ds.blah}"
896
+ end
897
+ end
898
+ def @dataset.blah
899
+ "ds"
900
+ end
901
+ @dataset.literal(@a.new).should == "called ds"
902
+ end
903
+
904
+ specify "should raise an error for unsupported types with no sql_literal method" do
905
+ proc {@dataset.literal(Object.new)}.should raise_error
906
+ end
907
+
908
+ specify "should literalize datasets as subqueries" do
909
+ d = @dataset.from(:test)
910
+ d.literal(d).should == "(#{d.sql})"
911
+ end
912
+
913
+ specify "should literalize Time properly" do
914
+ t = Time.now
915
+ s = t.strftime("'%Y-%m-%d %H:%M:%S")
916
+ @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
917
+ end
918
+
919
+ specify "should literalize DateTime properly" do
920
+ t = DateTime.now
921
+ s = t.strftime("'%Y-%m-%d %H:%M:%S")
922
+ @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.sec_fraction* 86400000000)}'"
923
+ end
924
+
925
+ specify "should literalize Date properly" do
926
+ d = Date.today
927
+ s = d.strftime("'%Y-%m-%d'")
928
+ @dataset.literal(d).should == s
929
+ end
930
+
931
+ specify "should literalize Date properly, even if to_s is overridden" do
932
+ d = Date.today
933
+ def d.to_s; "adsf" end
934
+ s = d.strftime("'%Y-%m-%d'")
935
+ @dataset.literal(d).should == s
936
+ end
937
+
938
+ specify "should literalize Time, DateTime, Date properly if SQL standard format is required" do
939
+ @dataset.meta_def(:requires_sql_standard_datetimes?){true}
940
+
941
+ t = Time.now
942
+ s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S")
943
+ @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
944
+
945
+ t = DateTime.now
946
+ s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S")
947
+ @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.sec_fraction* 86400000000)}'"
948
+
949
+ d = Date.today
950
+ s = d.strftime("DATE '%Y-%m-%d'")
951
+ @dataset.literal(d).should == s
952
+ end
953
+
954
+ specify "should literalize Time and DateTime properly if the database support timezones in timestamps" do
955
+ @dataset.meta_def(:supports_timestamp_timezones?){true}
956
+
957
+ t = Time.now.utc
958
+ s = t.strftime("'%Y-%m-%d %H:%M:%S")
959
+ @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}+0000'"
960
+
961
+ t = DateTime.now.new_offset(0)
962
+ s = t.strftime("'%Y-%m-%d %H:%M:%S")
963
+ @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.sec_fraction* 86400000000)}+0000'"
964
+ end
965
+
966
+ specify "should literalize Time and DateTime properly if the database doesn't support usecs in timestamps" do
967
+ @dataset.meta_def(:supports_timestamp_usecs?){false}
968
+
969
+ t = Time.now.utc
970
+ s = t.strftime("'%Y-%m-%d %H:%M:%S")
971
+ @dataset.literal(t).should == "#{s}'"
972
+
973
+ t = DateTime.now.new_offset(0)
974
+ s = t.strftime("'%Y-%m-%d %H:%M:%S")
975
+ @dataset.literal(t).should == "#{s}'"
976
+
977
+ @dataset.meta_def(:supports_timestamp_timezones?){true}
978
+
979
+ t = Time.now.utc
980
+ s = t.strftime("'%Y-%m-%d %H:%M:%S")
981
+ @dataset.literal(t).should == "#{s}+0000'"
982
+
983
+ t = DateTime.now.new_offset(0)
984
+ s = t.strftime("'%Y-%m-%d %H:%M:%S")
985
+ @dataset.literal(t).should == "#{s}+0000'"
986
+ end
987
+
988
+ specify "should not modify literal strings" do
989
+ @dataset.literal('col1 + 2'.lit).should == 'col1 + 2'
990
+
991
+ @dataset.update_sql(:a => 'a + 2'.lit).should ==
992
+ 'UPDATE test SET a = a + 2'
993
+ end
994
+
995
+ specify "should literalize BigDecimal instances correctly" do
996
+ @dataset.literal(BigDecimal.new("80")).should == "80.0"
997
+ @dataset.literal(BigDecimal.new("NaN")).should == "'NaN'"
998
+ @dataset.literal(BigDecimal.new("Infinity")).should == "'Infinity'"
999
+ @dataset.literal(BigDecimal.new("-Infinity")).should == "'-Infinity'"
1000
+ end
1001
+
1002
+ specify "should raise an Error if the object can't be literalized" do
1003
+ proc{@dataset.literal(Object.new)}.should raise_error(Sequel::Error)
1004
+ end
1005
+ end
1006
+
1007
+ context "Dataset#from" do
1008
+ before do
1009
+ @dataset = Sequel::Dataset.new(nil)
1010
+ end
1011
+
1012
+ specify "should accept a Dataset" do
1013
+ proc {@dataset.from(@dataset)}.should_not raise_error
1014
+ end
1015
+
1016
+ specify "should format a Dataset as a subquery if it has had options set" do
1017
+ @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
1018
+ "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) AS t1"
1019
+ end
1020
+
1021
+ specify "should automatically alias sub-queries" do
1022
+ @dataset.from(@dataset.from(:a).group(:b)).select_sql.should ==
1023
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1"
1024
+
1025
+ d1 = @dataset.from(:a).group(:b)
1026
+ d2 = @dataset.from(:c).group(:d)
1027
+
1028
+ @dataset.from(d1, d2).sql.should ==
1029
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1, (SELECT * FROM c GROUP BY d) AS t2"
1030
+ end
1031
+
1032
+ specify "should accept a hash for aliasing" do
1033
+ @dataset.from(:a => :b).sql.should ==
1034
+ "SELECT * FROM a AS b"
1035
+
1036
+ @dataset.from(:a => 'b').sql.should ==
1037
+ "SELECT * FROM a AS b"
1038
+
1039
+ @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should ==
1040
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
1041
+ end
1042
+
1043
+ specify "should always use a subquery if given a dataset" do
1044
+ @dataset.from(@dataset.from(:a)).select_sql.should ==
1045
+ "SELECT * FROM (SELECT * FROM a) AS t1"
1046
+ end
1047
+
1048
+ specify "should remove all FROM tables if called with no arguments" do
1049
+ @dataset.from.sql.should == 'SELECT *'
1050
+ end
1051
+
1052
+ specify "should accept sql functions" do
1053
+ @dataset.from(:abc.sql_function(:def)).select_sql.should ==
1054
+ "SELECT * FROM abc(def)"
1055
+
1056
+ @dataset.from(:a.sql_function(:i)).select_sql.should ==
1057
+ "SELECT * FROM a(i)"
1058
+ end
1059
+
1060
+ specify "should accept :schema__table___alias symbol format" do
1061
+ @dataset.from(:abc__def).select_sql.should ==
1062
+ "SELECT * FROM abc.def"
1063
+ @dataset.from(:abc__def___d).select_sql.should ==
1064
+ "SELECT * FROM abc.def AS d"
1065
+ @dataset.from(:abc___def).select_sql.should ==
1066
+ "SELECT * FROM abc AS def"
1067
+ end
1068
+ end
1069
+
1070
+ context "Dataset#select" do
1071
+ before do
1072
+ @d = Sequel::Dataset.new(nil).from(:test)
1073
+ end
1074
+
1075
+ specify "should accept variable arity" do
1076
+ @d.select(:name).sql.should == 'SELECT name FROM test'
1077
+ @d.select(:a, :b, :test__c).sql.should == 'SELECT a, b, test.c FROM test'
1078
+ end
1079
+
1080
+ specify "should accept symbols and literal strings" do
1081
+ @d.select('aaa'.lit).sql.should == 'SELECT aaa FROM test'
1082
+ @d.select(:a, 'b'.lit).sql.should == 'SELECT a, b FROM test'
1083
+ @d.select(:test__cc, 'test.d AS e'.lit).sql.should ==
1084
+ 'SELECT test.cc, test.d AS e FROM test'
1085
+ @d.select('test.d AS e'.lit, :test__cc).sql.should ==
1086
+ 'SELECT test.d AS e, test.cc FROM test'
1087
+
1088
+ # symbol helpers
1089
+ @d.select(:test.*).sql.should ==
1090
+ 'SELECT test.* FROM test'
1091
+ @d.select(:test__name.as(:n)).sql.should ==
1092
+ 'SELECT test.name AS n FROM test'
1093
+ @d.select(:test__name___n).sql.should ==
1094
+ 'SELECT test.name AS n FROM test'
1095
+ end
1096
+
1097
+ specify "should use the wildcard if no arguments are given" do
1098
+ @d.select.sql.should == 'SELECT * FROM test'
1099
+ end
1100
+
1101
+ specify "should accept a hash for AS values" do
1102
+ @d.select(:name => 'n', :__ggh => 'age').sql.should =~
1103
+ /SELECT ((name AS n, __ggh AS age)|(__ggh AS age, name AS n)) FROM test/
1104
+ end
1105
+
1106
+ specify "should overrun the previous select option" do
1107
+ @d.select!(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
1108
+ @d.select!(:price).select(:name).sql.should == 'SELECT name FROM test'
1109
+ end
1110
+
1111
+ specify "should accept arbitrary objects and literalize them correctly" do
1112
+ @d.select(1, :a, 't').sql.should == "SELECT 1, a, 't' FROM test"
1113
+
1114
+ @d.select(nil, :sum.sql_function(:t), :x___y).sql.should == "SELECT NULL, sum(t), x AS y FROM test"
1115
+
1116
+ @d.select(nil, 1, :x => :y).sql.should == "SELECT NULL, 1, x AS y FROM test"
1117
+ end
1118
+
1119
+ specify "should accept a block that yields a virtual row" do
1120
+ @d.select{|o| o.a}.sql.should == 'SELECT a FROM test'
1121
+ @d.select{a(1)}.sql.should == 'SELECT a(1) FROM test'
1122
+ @d.select{|o| o.a(1, 2)}.sql.should == 'SELECT a(1, 2) FROM test'
1123
+ @d.select{[a, a(1, 2)]}.sql.should == 'SELECT a, a(1, 2) FROM test'
1124
+ end
1125
+
1126
+ specify "should merge regular arguments with argument returned from block" do
1127
+ @d.select(:b){a}.sql.should == 'SELECT b, a FROM test'
1128
+ @d.select(:b, :c){|o| o.a(1)}.sql.should == 'SELECT b, c, a(1) FROM test'
1129
+ @d.select(:b){[a, a(1, 2)]}.sql.should == 'SELECT b, a, a(1, 2) FROM test'
1130
+ @d.select(:b, :c){|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT b, c, a, a(1, 2) FROM test'
1131
+ end
1132
+ end
1133
+
1134
+ context "Dataset#select_all" do
1135
+ before do
1136
+ @d = Sequel::Dataset.new(nil).from(:test)
1137
+ end
1138
+
1139
+ specify "should select the wildcard" do
1140
+ @d.select_all.sql.should == 'SELECT * FROM test'
1141
+ end
1142
+
1143
+ specify "should overrun the previous select option" do
1144
+ @d.select!(:a, :b, :c).select_all.sql.should == 'SELECT * FROM test'
1145
+ end
1146
+ end
1147
+
1148
+ context "Dataset#select_more" do
1149
+ before do
1150
+ @d = Sequel::Dataset.new(nil).from(:test)
1151
+ end
1152
+
1153
+ specify "should act like #select for datasets with no selection" do
1154
+ @d.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
1155
+ @d.select_all.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
1156
+ @d.select(:blah).select_all.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
1157
+ end
1158
+
1159
+ specify "should add to the currently selected columns" do
1160
+ @d.select(:a).select_more(:b).sql.should == 'SELECT a, b FROM test'
1161
+ @d.select(:a.*).select_more(:b.*).sql.should == 'SELECT a.*, b.* FROM test'
1162
+ end
1163
+
1164
+ specify "should accept a block that yields a virtual row" do
1165
+ @d.select(:a).select_more{|o| o.b}.sql.should == 'SELECT a, b FROM test'
1166
+ @d.select(:a.*).select_more(:b.*){b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
1167
+ end
1168
+ end
1169
+
1170
+ context "Dataset#select_append" do
1171
+ before do
1172
+ @d = Sequel::Dataset.new(nil).from(:test)
1173
+ end
1174
+
1175
+ specify "should select * in addition to columns if no columns selected" do
1176
+ @d.select_append(:a, :b).sql.should == 'SELECT *, a, b FROM test'
1177
+ @d.select_all.select_append(:a, :b).sql.should == 'SELECT *, a, b FROM test'
1178
+ @d.select(:blah).select_all.select_append(:a, :b).sql.should == 'SELECT *, a, b FROM test'
1179
+ end
1180
+
1181
+ specify "should add to the currently selected columns" do
1182
+ @d.select(:a).select_append(:b).sql.should == 'SELECT a, b FROM test'
1183
+ @d.select(:a.*).select_append(:b.*).sql.should == 'SELECT a.*, b.* FROM test'
1184
+ end
1185
+
1186
+ specify "should accept a block that yields a virtual row" do
1187
+ @d.select(:a).select_append{|o| o.b}.sql.should == 'SELECT a, b FROM test'
1188
+ @d.select(:a.*).select_append(:b.*){b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
1189
+ end
1190
+ end
1191
+
1192
+ context "Dataset#order" do
1193
+ before do
1194
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1195
+ end
1196
+
1197
+ specify "should include an ORDER BY clause in the select statement" do
1198
+ @dataset.order(:name).sql.should ==
1199
+ 'SELECT * FROM test ORDER BY name'
1200
+ end
1201
+
1202
+ specify "should accept multiple arguments" do
1203
+ @dataset.order(:name, :price.desc).sql.should ==
1204
+ 'SELECT * FROM test ORDER BY name, price DESC'
1205
+ end
1206
+
1207
+ specify "should overrun a previous ordering" do
1208
+ @dataset.order(:name).order(:stamp).sql.should ==
1209
+ 'SELECT * FROM test ORDER BY stamp'
1210
+ end
1211
+
1212
+ specify "should accept a literal string" do
1213
+ @dataset.order('dada ASC'.lit).sql.should ==
1214
+ 'SELECT * FROM test ORDER BY dada ASC'
1215
+ end
1216
+
1217
+ specify "should accept a hash as an expression" do
1218
+ @dataset.order(:name=>nil).sql.should ==
1219
+ 'SELECT * FROM test ORDER BY (name IS NULL)'
1220
+ end
1221
+
1222
+ specify "should accept a nil to remove ordering" do
1223
+ @dataset.order(:bah).order(nil).sql.should ==
1224
+ 'SELECT * FROM test'
1225
+ end
1226
+
1227
+ specify "should accept a block that yields a virtual row" do
1228
+ @dataset.order{|o| o.a}.sql.should == 'SELECT * FROM test ORDER BY a'
1229
+ @dataset.order{a(1)}.sql.should == 'SELECT * FROM test ORDER BY a(1)'
1230
+ @dataset.order{|o| o.a(1, 2)}.sql.should == 'SELECT * FROM test ORDER BY a(1, 2)'
1231
+ @dataset.order{[a, a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY a, a(1, 2)'
1232
+ end
1233
+
1234
+ specify "should merge regular arguments with argument returned from block" do
1235
+ @dataset.order(:b){a}.sql.should == 'SELECT * FROM test ORDER BY b, a'
1236
+ @dataset.order(:b, :c){|o| o.a(1)}.sql.should == 'SELECT * FROM test ORDER BY b, c, a(1)'
1237
+ @dataset.order(:b){[a, a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY b, a, a(1, 2)'
1238
+ @dataset.order(:b, :c){|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY b, c, a, a(1, 2)'
1239
+ end
1240
+ end
1241
+
1242
+ context "Dataset#unfiltered" do
1243
+ specify "should remove filtering from the dataset" do
1244
+ Sequel::Dataset.new(nil).from(:test).filter(:score=>1).unfiltered.sql.should == 'SELECT * FROM test'
1245
+ end
1246
+ end
1247
+
1248
+ context "Dataset#unlimited" do
1249
+ specify "should remove limit and offset from the dataset" do
1250
+ Sequel::Dataset.new(nil).from(:test).limit(1, 2).unlimited.sql.should == 'SELECT * FROM test'
1251
+ end
1252
+ end
1253
+
1254
+ context "Dataset#ungrouped" do
1255
+ specify "should remove group and having clauses from the dataset" do
1256
+ Sequel::Dataset.new(nil).from(:test).group(:a).having(:b).ungrouped.sql.should == 'SELECT * FROM test'
1257
+ end
1258
+ end
1259
+
1260
+ context "Dataset#unordered" do
1261
+ specify "should remove ordering from the dataset" do
1262
+ Sequel::Dataset.new(nil).from(:test).order(:name).unordered.sql.should == 'SELECT * FROM test'
1263
+ end
1264
+ end
1265
+
1266
+ context "Dataset#with_sql" do
1267
+ before do
1268
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1269
+ end
1270
+
1271
+ specify "should use static sql" do
1272
+ @dataset.with_sql('SELECT 1 FROM test').sql.should == 'SELECT 1 FROM test'
1273
+ end
1274
+
1275
+ specify "should work with placeholders" do
1276
+ @dataset.with_sql('SELECT ? FROM test', 1).sql.should == 'SELECT 1 FROM test'
1277
+ end
1278
+
1279
+ specify "should work with named placeholders" do
1280
+ @dataset.with_sql('SELECT :x FROM test', :x=>1).sql.should == 'SELECT 1 FROM test'
1281
+ end
1282
+
1283
+ specify "should keep row_proc" do
1284
+ @dataset.with_sql('SELECT 1 FROM test').row_proc.should == @dataset.row_proc
1285
+ end
1286
+ end
1287
+
1288
+ context "Dataset#order_by" do
1289
+ before do
1290
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1291
+ end
1292
+
1293
+ specify "should include an ORDER BY clause in the select statement" do
1294
+ @dataset.order_by(:name).sql.should ==
1295
+ 'SELECT * FROM test ORDER BY name'
1296
+ end
1297
+
1298
+ specify "should accept multiple arguments" do
1299
+ @dataset.order_by(:name, :price.desc).sql.should ==
1300
+ 'SELECT * FROM test ORDER BY name, price DESC'
1301
+ end
1302
+
1303
+ specify "should overrun a previous ordering" do
1304
+ @dataset.order_by(:name).order(:stamp).sql.should ==
1305
+ 'SELECT * FROM test ORDER BY stamp'
1306
+ end
1307
+
1308
+ specify "should accept a string" do
1309
+ @dataset.order_by('dada ASC'.lit).sql.should ==
1310
+ 'SELECT * FROM test ORDER BY dada ASC'
1311
+ end
1312
+
1313
+ specify "should accept a nil to remove ordering" do
1314
+ @dataset.order_by(:bah).order_by(nil).sql.should ==
1315
+ 'SELECT * FROM test'
1316
+ end
1317
+ end
1318
+
1319
+ context "Dataset#order_more" do
1320
+ before do
1321
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1322
+ end
1323
+
1324
+ specify "should include an ORDER BY clause in the select statement" do
1325
+ @dataset.order_more(:name).sql.should ==
1326
+ 'SELECT * FROM test ORDER BY name'
1327
+ end
1328
+
1329
+ specify "should add to a previous ordering" do
1330
+ @dataset.order(:name).order_more(:stamp.desc).sql.should ==
1331
+ 'SELECT * FROM test ORDER BY name, stamp DESC'
1332
+ end
1333
+
1334
+ specify "should accept a block that yields a virtual row" do
1335
+ @dataset.order(:a).order_more{|o| o.b}.sql.should == 'SELECT * FROM test ORDER BY a, b'
1336
+ @dataset.order(:a, :b).order_more(:c, :d){[e, f(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY a, b, c, d, e, f(1, 2)'
1337
+ end
1338
+ end
1339
+
1340
+ context "Dataset#reverse_order" do
1341
+ before do
1342
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1343
+ end
1344
+
1345
+ specify "should use DESC as default order" do
1346
+ @dataset.reverse_order(:name).sql.should ==
1347
+ 'SELECT * FROM test ORDER BY name DESC'
1348
+ end
1349
+
1350
+ specify "should invert the order given" do
1351
+ @dataset.reverse_order(:name.desc).sql.should ==
1352
+ 'SELECT * FROM test ORDER BY name ASC'
1353
+ end
1354
+
1355
+ specify "should invert the order for ASC expressions" do
1356
+ @dataset.reverse_order(:name.asc).sql.should ==
1357
+ 'SELECT * FROM test ORDER BY name DESC'
1358
+ end
1359
+
1360
+ specify "should accept multiple arguments" do
1361
+ @dataset.reverse_order(:name, :price.desc).sql.should ==
1362
+ 'SELECT * FROM test ORDER BY name DESC, price ASC'
1363
+ end
1364
+
1365
+ specify "should reverse a previous ordering if no arguments are given" do
1366
+ @dataset.order(:name).reverse_order.sql.should ==
1367
+ 'SELECT * FROM test ORDER BY name DESC'
1368
+ @dataset.order(:clumsy.desc, :fool).reverse_order.sql.should ==
1369
+ 'SELECT * FROM test ORDER BY clumsy ASC, fool DESC'
1370
+ end
1371
+
1372
+ specify "should return an unordered dataset for a dataset with no order" do
1373
+ @dataset.unordered.reverse_order.sql.should ==
1374
+ 'SELECT * FROM test'
1375
+ end
1376
+
1377
+ specify "should have #reverse alias" do
1378
+ @dataset.order(:name).reverse.sql.should ==
1379
+ 'SELECT * FROM test ORDER BY name DESC'
1380
+ end
1381
+ end
1382
+
1383
+ context "Dataset#limit" do
1384
+ before do
1385
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1386
+ end
1387
+
1388
+ specify "should include a LIMIT clause in the select statement" do
1389
+ @dataset.limit(10).sql.should ==
1390
+ 'SELECT * FROM test LIMIT 10'
1391
+ end
1392
+
1393
+ specify "should accept ranges" do
1394
+ @dataset.limit(3..7).sql.should ==
1395
+ 'SELECT * FROM test LIMIT 5 OFFSET 3'
1396
+
1397
+ @dataset.limit(3...7).sql.should ==
1398
+ 'SELECT * FROM test LIMIT 4 OFFSET 3'
1399
+ end
1400
+
1401
+ specify "should include an offset if a second argument is given" do
1402
+ @dataset.limit(6, 10).sql.should ==
1403
+ 'SELECT * FROM test LIMIT 6 OFFSET 10'
1404
+ end
1405
+
1406
+ specify "should convert regular strings to integers" do
1407
+ @dataset.limit('6', 'a() - 1').sql.should ==
1408
+ 'SELECT * FROM test LIMIT 6 OFFSET 0'
1409
+ end
1410
+
1411
+ specify "should not convert literal strings to integers" do
1412
+ @dataset.limit('6'.lit, 'a() - 1'.lit).sql.should ==
1413
+ 'SELECT * FROM test LIMIT 6 OFFSET a() - 1'
1414
+ end
1415
+
1416
+ specify "should not convert other objects" do
1417
+ @dataset.limit(6, :a.sql_function - 1).sql.should ==
1418
+ 'SELECT * FROM test LIMIT 6 OFFSET (a() - 1)'
1419
+ end
1420
+
1421
+ specify "should work with fixed sql datasets" do
1422
+ @dataset.opts[:sql] = 'select * from cccc'
1423
+ @dataset.limit(6, 10).sql.should ==
1424
+ 'SELECT * FROM (select * from cccc) AS t1 LIMIT 6 OFFSET 10'
1425
+ end
1426
+
1427
+ specify "should raise an error if an invalid limit or offset is used" do
1428
+ proc{@dataset.limit(-1)}.should raise_error(Sequel::Error)
1429
+ proc{@dataset.limit(0)}.should raise_error(Sequel::Error)
1430
+ proc{@dataset.limit(1)}.should_not raise_error(Sequel::Error)
1431
+ proc{@dataset.limit(1, -1)}.should raise_error(Sequel::Error)
1432
+ proc{@dataset.limit(1, 0)}.should_not raise_error(Sequel::Error)
1433
+ proc{@dataset.limit(1, 1)}.should_not raise_error(Sequel::Error)
1434
+ end
1435
+ end
1436
+
1437
+ context "Dataset#naked" do
1438
+ before do
1439
+ @d1 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4})
1440
+ @d2 = @d1.clone
1441
+ @d2.row_proc = Proc.new{|r| r}
1442
+ end
1443
+
1444
+ specify "should remove any existing row_proc" do
1445
+ naked = @d2.naked
1446
+ naked.row_proc.should be_nil
1447
+ end
1448
+ end
1449
+
1450
+ context "Dataset#qualified_column_name" do
1451
+ before do
1452
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1453
+ end
1454
+
1455
+ specify "should return the literal value if not given a symbol" do
1456
+ @dataset.literal(@dataset.send(:qualified_column_name, 'ccc__b', :items)).should == "'ccc__b'"
1457
+ @dataset.literal(@dataset.send(:qualified_column_name, 3, :items)).should == '3'
1458
+ @dataset.literal(@dataset.send(:qualified_column_name, 'a'.lit, :items)).should == 'a'
1459
+ end
1460
+
1461
+ specify "should qualify the column with the supplied table name if given an unqualified symbol" do
1462
+ @dataset.literal(@dataset.send(:qualified_column_name, :b1, :items)).should == 'items.b1'
1463
+ end
1464
+
1465
+ specify "should not changed the qualifed column's table if given a qualified symbol" do
1466
+ @dataset.literal(@dataset.send(:qualified_column_name, :ccc__b, :items)).should == 'ccc.b'
1467
+ end
1468
+ end
1469
+
1470
+ class DummyDataset < Sequel::Dataset
1471
+ VALUES = [
1472
+ {:a => 1, :b => 2},
1473
+ {:a => 3, :b => 4},
1474
+ {:a => 5, :b => 6}
1475
+ ]
1476
+ def fetch_rows(sql, &block)
1477
+ VALUES.each(&block)
1478
+ end
1479
+ end
1480
+
1481
+ context "Dataset#map" do
1482
+ before do
1483
+ @d = DummyDataset.new(nil).from(:items)
1484
+ end
1485
+
1486
+ specify "should provide the usual functionality if no argument is given" do
1487
+ @d.map {|n| n[:a] + n[:b]}.should == [3, 7, 11]
1488
+ end
1489
+
1490
+ specify "should map using #[column name] if column name is given" do
1491
+ @d.map(:a).should == [1, 3, 5]
1492
+ end
1493
+
1494
+ specify "should return the complete dataset values if nothing is given" do
1495
+ @d.map.to_a.should == DummyDataset::VALUES
1496
+ end
1497
+ end
1498
+
1499
+ context "Dataset#to_hash" do
1500
+ before do
1501
+ @d = DummyDataset.new(nil).from(:items)
1502
+ end
1503
+
1504
+ specify "should provide a hash with the first column as key and the second as value" do
1505
+ @d.to_hash(:a, :b).should == {1 => 2, 3 => 4, 5 => 6}
1506
+ @d.to_hash(:b, :a).should == {2 => 1, 4 => 3, 6 => 5}
1507
+ end
1508
+
1509
+ specify "should provide a hash with the first column as key and the entire hash as value if the value column is blank or nil" do
1510
+ @d.to_hash(:a).should == {1 => {:a => 1, :b => 2}, 3 => {:a => 3, :b => 4}, 5 => {:a => 5, :b => 6}}
1511
+ @d.to_hash(:b).should == {2 => {:a => 1, :b => 2}, 4 => {:a => 3, :b => 4}, 6 => {:a => 5, :b => 6}}
1512
+ end
1513
+ end
1514
+
1515
+ context "Dataset#distinct" do
1516
+ before do
1517
+ @db = MockDatabase.new
1518
+ @dataset = @db[:test].select(:name)
1519
+ end
1520
+
1521
+ specify "should include DISTINCT clause in statement" do
1522
+ @dataset.distinct.sql.should == 'SELECT DISTINCT name FROM test'
1523
+ end
1524
+
1525
+ specify "should raise an error if columns given and DISTINCT ON is not supported" do
1526
+ proc{@dataset.distinct}.should_not raise_error
1527
+ proc{@dataset.distinct(:a)}.should raise_error(Sequel::InvalidOperation)
1528
+ end
1529
+
1530
+ specify "should use DISTINCT ON if columns are given and DISTINCT ON is supported" do
1531
+ @dataset.meta_def(:supports_distinct_on?){true}
1532
+ @dataset.distinct(:a, :b).sql.should == 'SELECT DISTINCT ON (a, b) name FROM test'
1533
+ @dataset.distinct(:stamp.cast(:integer), :node_id=>nil).sql.should == 'SELECT DISTINCT ON (CAST(stamp AS integer), (node_id IS NULL)) name FROM test'
1534
+ end
1535
+
1536
+ specify "should do a subselect for count" do
1537
+ @dataset.distinct.count
1538
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM (SELECT DISTINCT name FROM test) AS t1 LIMIT 1']
1539
+ end
1540
+ end
1541
+
1542
+ context "Dataset#count" do
1543
+ before do
1544
+ @c = Class.new(Sequel::Dataset) do
1545
+ def self.sql
1546
+ @@sql
1547
+ end
1548
+
1549
+ def fetch_rows(sql)
1550
+ @columns = [sql =~ /SELECT COUNT/i ? :count : :a]
1551
+ @@sql = sql
1552
+ yield({@columns.first=>1})
1553
+ end
1554
+ end
1555
+ @dataset = @c.new(nil).from(:test)
1556
+ end
1557
+
1558
+ specify "should format SQL properly" do
1559
+ @dataset.count.should == 1
1560
+ @c.sql.should == 'SELECT COUNT(*) AS count FROM test LIMIT 1'
1561
+ end
1562
+
1563
+ specify "should include the where clause if it's there" do
1564
+ @dataset.filter(:abc.sql_number < 30).count.should == 1
1565
+ @c.sql.should == 'SELECT COUNT(*) AS count FROM test WHERE (abc < 30) LIMIT 1'
1566
+ end
1567
+
1568
+ specify "should count properly for datasets with fixed sql" do
1569
+ @dataset.opts[:sql] = "select abc from xyz"
1570
+ @dataset.count.should == 1
1571
+ @c.sql.should == "SELECT COUNT(*) AS count FROM (select abc from xyz) AS t1 LIMIT 1"
1572
+ end
1573
+
1574
+ specify "should count properly when using UNION, INTERSECT, or EXCEPT" do
1575
+ @dataset.union(@dataset).count.should == 1
1576
+ @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 1"
1577
+ @dataset.intersect(@dataset).count.should == 1
1578
+ @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test INTERSECT SELECT * FROM test) AS t1 LIMIT 1"
1579
+ @dataset.except(@dataset).count.should == 1
1580
+ @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test EXCEPT SELECT * FROM test) AS t1 LIMIT 1"
1581
+ end
1582
+
1583
+ specify "should return limit if count is greater than it" do
1584
+ @dataset.limit(5).count.should == 1
1585
+ @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"
1586
+ end
1587
+
1588
+ it "should work on a graphed_dataset" do
1589
+ @dataset.should_receive(:columns).twice.and_return([:a])
1590
+ @dataset.graph(@dataset, [:a], :table_alias=>:test2).count.should == 1
1591
+ @c.sql.should == 'SELECT COUNT(*) AS count FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1'
1592
+ end
1593
+
1594
+ specify "should not cache the columns value" do
1595
+ ds = @dataset.from(:blah)
1596
+ ds.columns.should == [:a]
1597
+ ds.count.should == 1
1598
+ @c.sql.should == 'SELECT COUNT(*) AS count FROM blah LIMIT 1'
1599
+ ds.columns.should == [:a]
1600
+ end
1601
+ end
1602
+
1603
+
1604
+ context "Dataset#group_and_count" do
1605
+ before do
1606
+ @c = Class.new(Sequel::Dataset) do
1607
+ def self.sql
1608
+ @@sql
1609
+ end
1610
+
1611
+ def fetch_rows(sql)
1612
+ @@sql = sql
1613
+ yield({1 => 1})
1614
+ end
1615
+ end
1616
+ @ds = @c.new(nil).from(:test)
1617
+ end
1618
+
1619
+ specify "should format SQL properly" do
1620
+ @ds.group_and_count(:name).sql.should ==
1621
+ "SELECT name, count(*) AS count FROM test GROUP BY name"
1622
+ end
1623
+
1624
+ specify "should accept multiple columns for grouping" do
1625
+ @ds.group_and_count(:a, :b).sql.should ==
1626
+ "SELECT a, b, count(*) AS count FROM test GROUP BY a, b"
1627
+ end
1628
+
1629
+ specify "should format column aliases in the select clause but not in the group clause" do
1630
+ @ds.group_and_count(:name___n).sql.should ==
1631
+ "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1632
+ @ds.group_and_count(:name__n).sql.should ==
1633
+ "SELECT name.n, count(*) AS count FROM test GROUP BY name.n"
1634
+ end
1635
+
1636
+ specify "should handle identifiers" do
1637
+ @ds.group_and_count(:name___n.identifier).sql.should ==
1638
+ "SELECT name___n, count(*) AS count FROM test GROUP BY name___n"
1639
+ end
1640
+
1641
+ specify "should handle literal strings" do
1642
+ @ds.group_and_count("name".lit).sql.should ==
1643
+ "SELECT name, count(*) AS count FROM test GROUP BY name"
1644
+ end
1645
+
1646
+ specify "should handle aliased expressions" do
1647
+ @ds.group_and_count(:name.as(:n)).sql.should ==
1648
+ "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1649
+ @ds.group_and_count(:name.identifier.as(:n)).sql.should ==
1650
+ "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1651
+ end
1652
+ end
1653
+
1654
+ context "Dataset#empty?" do
1655
+ specify "should return true if records exist in the dataset" do
1656
+ @c = Class.new(Sequel::Dataset) do
1657
+ def self.sql
1658
+ @@sql
1659
+ end
1660
+
1661
+ def fetch_rows(sql)
1662
+ @@sql = sql
1663
+ yield({1 => 1}) unless sql =~ /WHERE 'f'/
1664
+ end
1665
+ end
1666
+ @c.new(nil).from(:test).should_not be_empty
1667
+ @c.sql.should == 'SELECT 1 FROM test LIMIT 1'
1668
+ @c.new(nil).from(:test).filter(false).should be_empty
1669
+ @c.sql.should == "SELECT 1 FROM test WHERE 'f' LIMIT 1"
1670
+ end
1671
+ end
1672
+
1673
+ context "Dataset#first_source_alias" do
1674
+ before do
1675
+ @ds = Sequel::Dataset.new(nil)
1676
+ end
1677
+
1678
+ specify "should be the entire first source if not aliased" do
1679
+ @ds.from(:t).first_source_alias.should == :t
1680
+ @ds.from(:t__a.identifier).first_source_alias.should == :t__a.identifier
1681
+ @ds.from(:s__t).first_source_alias.should == :s__t
1682
+ @ds.from(:t.qualify(:s)).first_source_alias.should == :t.qualify(:s)
1683
+ end
1684
+
1685
+ specify "should be the alias if aliased" do
1686
+ @ds.from(:t___a).first_source_alias.should == :a
1687
+ @ds.from(:s__t___a).first_source_alias.should == :a
1688
+ @ds.from(:t.as(:a)).first_source_alias.should == :a
1689
+ end
1690
+
1691
+ specify "should be aliased as first_source" do
1692
+ @ds.from(:t).first_source.should == :t
1693
+ @ds.from(:t__a.identifier).first_source.should == :t__a.identifier
1694
+ @ds.from(:s__t___a).first_source.should == :a
1695
+ @ds.from(:t.as(:a)).first_source.should == :a
1696
+ end
1697
+
1698
+ specify "should raise exception if table doesn't have a source" do
1699
+ proc{@ds.first_source_alias.should == :t}.should raise_error(Sequel::Error)
1700
+ end
1701
+ end
1702
+
1703
+ context "Dataset#first_source_table" do
1704
+ before do
1705
+ @ds = Sequel::Dataset.new(nil)
1706
+ end
1707
+
1708
+ specify "should be the entire first source if not aliased" do
1709
+ @ds.from(:t).first_source_table.should == :t
1710
+ @ds.from(:t__a.identifier).first_source_table.should == :t__a.identifier
1711
+ @ds.from(:s__t).first_source_table.should == :s__t
1712
+ @ds.from(:t.qualify(:s)).first_source_table.should == :t.qualify(:s)
1713
+ end
1714
+
1715
+ specify "should be the unaliased part if aliased" do
1716
+ @ds.from(:t___a).first_source_table.should == :t.identifier
1717
+ @ds.from(:s__t___a).first_source_table.should == :t.qualify(:s)
1718
+ @ds.from(:t.as(:a)).first_source_table.should == :t
1719
+ end
1720
+
1721
+ specify "should raise exception if table doesn't have a source" do
1722
+ proc{@ds.first_source_table.should == :t}.should raise_error(Sequel::Error)
1723
+ end
1724
+ end
1725
+
1726
+ context "Dataset#from_self" do
1727
+ before do
1728
+ @ds = Sequel::Dataset.new(nil).from(:test).select(:name).limit(1)
1729
+ end
1730
+
1731
+ specify "should set up a default alias" do
1732
+ @ds.from_self.sql.should == 'SELECT * FROM (SELECT name FROM test LIMIT 1) AS t1'
1733
+ end
1734
+
1735
+ specify "should modify only the new dataset" do
1736
+ @ds.from_self.select(:bogus).sql.should == 'SELECT bogus FROM (SELECT name FROM test LIMIT 1) AS t1'
1737
+ end
1738
+
1739
+ specify "should use the user-specified alias" do
1740
+ @ds.from_self(:alias=>:some_name).sql.should == 'SELECT * FROM (SELECT name FROM test LIMIT 1) AS some_name'
1741
+ end
1742
+
1743
+ specify "should use the user-specified alias for joins" do
1744
+ @ds.from_self(:alias=>:some_name).inner_join(:posts, :alias=>:name).sql.should == \
1745
+ 'SELECT * FROM (SELECT name FROM test LIMIT 1) AS some_name INNER JOIN posts ON (posts.alias = some_name.name)'
1746
+ end
1747
+
1748
+ specify "should not options such as server" do
1749
+ @ds.server(:blah).from_self(:alias=>:some_name).opts[:server].should == :blah
1750
+ end
1751
+
1752
+ end
1753
+
1754
+ context "Dataset#join_table" do
1755
+ before do
1756
+ @d = MockDataset.new(nil).from(:items)
1757
+ @d.quote_identifiers = true
1758
+ end
1759
+
1760
+ specify "should format the JOIN clause properly" do
1761
+ @d.join_table(:left_outer, :categories, :category_id => :id).sql.should ==
1762
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1763
+ end
1764
+
1765
+ specify "should handle multiple conditions on the same join table column" do
1766
+ @d.join_table(:left_outer, :categories, [[:category_id, :id], [:category_id, 0..100]]).sql.should ==
1767
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON (("categories"."category_id" = "items"."id") AND ("categories"."category_id" >= 0) AND ("categories"."category_id" <= 100))'
1768
+ end
1769
+
1770
+ specify "should include WHERE clause if applicable" do
1771
+ @d.filter(:price.sql_number < 100).join_table(:right_outer, :categories, :category_id => :id).sql.should ==
1772
+ 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") WHERE ("price" < 100)'
1773
+ end
1774
+
1775
+ specify "should include ORDER BY clause if applicable" do
1776
+ @d.order(:stamp).join_table(:full_outer, :categories, :category_id => :id).sql.should ==
1777
+ 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") ORDER BY "stamp"'
1778
+ end
1779
+
1780
+ specify "should support multiple joins" do
1781
+ @d.join_table(:inner, :b, :items_id=>:id).join_table(:left_outer, :c, :b_id => :b__id).sql.should ==
1782
+ 'SELECT * FROM "items" INNER JOIN "b" ON ("b"."items_id" = "items"."id") LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")'
1783
+ end
1784
+
1785
+ specify "should support arbitrary join types" do
1786
+ @d.join_table(:magic, :categories, :category_id=>:id).sql.should ==
1787
+ 'SELECT * FROM "items" MAGIC JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1788
+ end
1789
+
1790
+ specify "should support many join methods" do
1791
+ @d.left_outer_join(:categories, :category_id=>:id).sql.should ==
1792
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1793
+ @d.right_outer_join(:categories, :category_id=>:id).sql.should ==
1794
+ 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1795
+ @d.full_outer_join(:categories, :category_id=>:id).sql.should ==
1796
+ 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1797
+ @d.inner_join(:categories, :category_id=>:id).sql.should ==
1798
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1799
+ @d.left_join(:categories, :category_id=>:id).sql.should ==
1800
+ 'SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1801
+ @d.right_join(:categories, :category_id=>:id).sql.should ==
1802
+ 'SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1803
+ @d.full_join(:categories, :category_id=>:id).sql.should ==
1804
+ 'SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1805
+ @d.natural_join(:categories).sql.should ==
1806
+ 'SELECT * FROM "items" NATURAL JOIN "categories"'
1807
+ @d.natural_left_join(:categories).sql.should ==
1808
+ 'SELECT * FROM "items" NATURAL LEFT JOIN "categories"'
1809
+ @d.natural_right_join(:categories).sql.should ==
1810
+ 'SELECT * FROM "items" NATURAL RIGHT JOIN "categories"'
1811
+ @d.natural_full_join(:categories).sql.should ==
1812
+ 'SELECT * FROM "items" NATURAL FULL JOIN "categories"'
1813
+ @d.cross_join(:categories).sql.should ==
1814
+ 'SELECT * FROM "items" CROSS JOIN "categories"'
1815
+ end
1816
+
1817
+ specify "should raise an error if additional arguments are provided to join methods that don't take conditions" do
1818
+ proc{@d.natural_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
1819
+ proc{@d.natural_left_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
1820
+ proc{@d.natural_right_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
1821
+ proc{@d.natural_full_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
1822
+ proc{@d.cross_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
1823
+ end
1824
+
1825
+ specify "should raise an error if blocks are provided to join methods that don't pass them" do
1826
+ proc{@d.natural_join(:categories){}}.should raise_error(Sequel::Error)
1827
+ proc{@d.natural_left_join(:categories){}}.should raise_error(Sequel::Error)
1828
+ proc{@d.natural_right_join(:categories){}}.should raise_error(Sequel::Error)
1829
+ proc{@d.natural_full_join(:categories){}}.should raise_error(Sequel::Error)
1830
+ proc{@d.cross_join(:categories){}}.should raise_error(Sequel::Error)
1831
+ end
1832
+
1833
+ specify "should default to a plain join if nil is used for the type" do
1834
+ @d.join_table(nil, :categories, :category_id=>:id).sql.should ==
1835
+ 'SELECT * FROM "items" JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1836
+ end
1837
+
1838
+ specify "should use an inner join for Dataset#join" do
1839
+ @d.join(:categories, :category_id=>:id).sql.should ==
1840
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1841
+ end
1842
+
1843
+ specify "should support aliased tables using the deprecated argument" do
1844
+ @d.from('stats').join('players', {:id => :player_id}, 'p').sql.should ==
1845
+ 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1846
+ end
1847
+
1848
+ specify "should support aliased tables using the :table_alias option" do
1849
+ @d.from('stats').join('players', {:id => :player_id}, :table_alias=>:p).sql.should ==
1850
+ 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1851
+ end
1852
+
1853
+ specify "should support using an alias for the FROM when doing the first join with unqualified condition columns" do
1854
+ ds = MockDataset.new(nil).from(:foo => :f)
1855
+ ds.quote_identifiers = true
1856
+ ds.join_table(:inner, :bar, :id => :bar_id).sql.should ==
1857
+ 'SELECT * FROM "foo" AS "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
1858
+ end
1859
+
1860
+ specify "should support implicit schemas in from table symbols" do
1861
+ @d.from(:s__t).join(:u__v, {:id => :player_id}).sql.should ==
1862
+ 'SELECT * FROM "s"."t" INNER JOIN "u"."v" ON ("u"."v"."id" = "s"."t"."player_id")'
1863
+ end
1864
+
1865
+ specify "should support implicit aliases in from table symbols" do
1866
+ @d.from(:t___z).join(:v___y, {:id => :player_id}).sql.should ==
1867
+ 'SELECT * FROM "t" AS "z" INNER JOIN "v" AS "y" ON ("y"."id" = "z"."player_id")'
1868
+ @d.from(:s__t___z).join(:u__v___y, {:id => :player_id}).sql.should ==
1869
+ 'SELECT * FROM "s"."t" AS "z" INNER JOIN "u"."v" AS "y" ON ("y"."id" = "z"."player_id")'
1870
+ end
1871
+
1872
+ specify "should support AliasedExpressions" do
1873
+ @d.from(:s.as(:t)).join(:u.as(:v), {:id => :player_id}).sql.should ==
1874
+ 'SELECT * FROM "s" AS "t" INNER JOIN "u" AS "v" ON ("v"."id" = "t"."player_id")'
1875
+ end
1876
+
1877
+ specify "should support the :implicit_qualifier option" do
1878
+ @d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should ==
1879
+ 'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
1880
+ end
1881
+
1882
+ specify "should allow for arbitrary conditions in the JOIN clause" do
1883
+ @d.join_table(:left_outer, :categories, :status => 0).sql.should ==
1884
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" = 0)'
1885
+ @d.join_table(:left_outer, :categories, :categorizable_type => "Post").sql.should ==
1886
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categorizable_type" = \'Post\')'
1887
+ @d.join_table(:left_outer, :categories, :timestamp => "CURRENT_TIMESTAMP".lit).sql.should ==
1888
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."timestamp" = CURRENT_TIMESTAMP)'
1889
+ @d.join_table(:left_outer, :categories, :status => [1, 2, 3]).sql.should ==
1890
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" IN (1, 2, 3))'
1891
+ end
1892
+
1893
+ specify "should raise error for a table without a source" do
1894
+ proc {Sequel::Dataset.new(nil).join('players', :id => :player_id)}. \
1895
+ should raise_error(Sequel::Error)
1896
+ end
1897
+
1898
+ specify "should support joining datasets" do
1899
+ ds = Sequel::Dataset.new(nil).from(:categories)
1900
+
1901
+ @d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
1902
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id")'
1903
+
1904
+ ds.filter!(:active => true)
1905
+
1906
+ @d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
1907
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t1" ON ("t1"."item_id" = "items"."id")'
1908
+
1909
+ @d.from_self.join_table(:left_outer, ds, :item_id => :id).sql.should ==
1910
+ 'SELECT * FROM (SELECT * FROM "items") AS "t1" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t2" ON ("t2"."item_id" = "t1"."id")'
1911
+ end
1912
+
1913
+ specify "should support joining datasets and aliasing the join" do
1914
+ ds = Sequel::Dataset.new(nil).from(:categories)
1915
+
1916
+ @d.join_table(:left_outer, ds, {:ds__item_id => :id}, :ds).sql.should ==
1917
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "ds" ON ("ds"."item_id" = "items"."id")'
1918
+ end
1919
+
1920
+ specify "should support joining multiple datasets" do
1921
+ ds = Sequel::Dataset.new(nil).from(:categories)
1922
+ ds2 = Sequel::Dataset.new(nil).from(:nodes).select(:name)
1923
+ ds3 = Sequel::Dataset.new(nil).from(:attributes).filter("name = 'blah'")
1924
+
1925
+ @d.join_table(:left_outer, ds, :item_id => :id).join_table(:inner, ds2, :node_id=>:id).join_table(:right_outer, ds3, :attribute_id=>:id).sql.should ==
1926
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id") ' \
1927
+ 'INNER JOIN (SELECT name FROM nodes) AS "t2" ON ("t2"."node_id" = "t1"."id") ' \
1928
+ 'RIGHT OUTER JOIN (SELECT * FROM attributes WHERE (name = \'blah\')) AS "t3" ON ("t3"."attribute_id" = "t2"."id")'
1929
+ end
1930
+
1931
+ specify "should support joining objects that respond to :table_name" do
1932
+ ds = Object.new
1933
+ def ds.table_name; :categories end
1934
+
1935
+ @d.join(ds, :item_id => :id).sql.should ==
1936
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."item_id" = "items"."id")'
1937
+ end
1938
+
1939
+ specify "should support using a SQL String as the join condition" do
1940
+ @d.join(:categories, %{c.item_id = items.id}, :c).sql.should ==
1941
+ 'SELECT * FROM "items" INNER JOIN "categories" AS "c" ON (c.item_id = items.id)'
1942
+ end
1943
+
1944
+ specify "should support using a boolean column as the join condition" do
1945
+ @d.join(:categories, :active).sql.should ==
1946
+ 'SELECT * FROM "items" INNER JOIN "categories" ON "active"'
1947
+ end
1948
+
1949
+ specify "should support using an expression as the join condition" do
1950
+ @d.join(:categories, :number.sql_number > 10).sql.should ==
1951
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("number" > 10)'
1952
+ end
1953
+
1954
+ specify "should support natural and cross joins using nil" do
1955
+ @d.join_table(:natural, :categories).sql.should ==
1956
+ 'SELECT * FROM "items" NATURAL JOIN "categories"'
1957
+ @d.join_table(:cross, :categories, nil).sql.should ==
1958
+ 'SELECT * FROM "items" CROSS JOIN "categories"'
1959
+ @d.join_table(:natural, :categories, nil, :c).sql.should ==
1960
+ 'SELECT * FROM "items" NATURAL JOIN "categories" AS "c"'
1961
+ end
1962
+
1963
+ specify "should support joins with a USING clause if an array of symbols is used" do
1964
+ @d.join(:categories, [:id]).sql.should ==
1965
+ 'SELECT * FROM "items" INNER JOIN "categories" USING ("id")'
1966
+ @d.join(:categories, [:id1, :id2]).sql.should ==
1967
+ 'SELECT * FROM "items" INNER JOIN "categories" USING ("id1", "id2")'
1968
+ end
1969
+
1970
+ specify "should emulate JOIN USING (poorly) if the dataset doesn't support it" do
1971
+ @d.meta_def(:supports_join_using?){false}
1972
+ @d.join(:categories, [:id]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."id" = "items"."id")'
1973
+ end
1974
+
1975
+ specify "should raise an error if using an array of symbols with a block" do
1976
+ proc{@d.join(:categories, [:id]){|j,lj,js|}}.should raise_error(Sequel::Error)
1977
+ end
1978
+
1979
+ specify "should support using a block that receieves the join table/alias, last join table/alias, and array of previous joins" do
1980
+ @d.join(:categories) do |join_alias, last_join_alias, joins|
1981
+ join_alias.should == :categories
1982
+ last_join_alias.should == :items
1983
+ joins.should == []
1984
+ end
1985
+
1986
+ @d.from(:items=>:i).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
1987
+ join_alias.should == :c
1988
+ last_join_alias.should == :i
1989
+ joins.should == []
1990
+ end
1991
+
1992
+ @d.from(:items___i).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
1993
+ join_alias.should == :c
1994
+ last_join_alias.should == :i
1995
+ joins.should == []
1996
+ end
1997
+
1998
+ @d.join(:blah).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
1999
+ join_alias.should == :c
2000
+ last_join_alias.should == :blah
2001
+ joins.should be_a_kind_of(Array)
2002
+ joins.length.should == 1
2003
+ joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
2004
+ joins.first.join_type.should == :inner
2005
+ end
2006
+
2007
+ @d.join_table(:natural, :blah, nil, :b).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
2008
+ join_alias.should == :c
2009
+ last_join_alias.should == :b
2010
+ joins.should be_a_kind_of(Array)
2011
+ joins.length.should == 1
2012
+ joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
2013
+ joins.first.join_type.should == :natural
2014
+ end
2015
+
2016
+ @d.join(:blah).join(:categories).join(:blah2) do |join_alias, last_join_alias, joins|
2017
+ join_alias.should == :blah2
2018
+ last_join_alias.should == :categories
2019
+ joins.should be_a_kind_of(Array)
2020
+ joins.length.should == 2
2021
+ joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
2022
+ joins.first.table.should == :blah
2023
+ joins.last.should be_a_kind_of(Sequel::SQL::JoinClause)
2024
+ joins.last.table.should == :categories
2025
+ end
2026
+ end
2027
+
2028
+ specify "should use the block result as the only condition if no condition is given" do
2029
+ @d.join(:categories){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
2030
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" = "items"."c")'
2031
+ @d.join(:categories){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
2032
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" > "items"."c")'
2033
+ end
2034
+
2035
+ specify "should combine the block conditions and argument conditions if both given" do
2036
+ @d.join(:categories, :a=>:d){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
2037
+ 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" = "items"."c"))'
2038
+ @d.join(:categories, :a=>:d){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
2039
+ 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" > "items"."c"))'
2040
+ end
2041
+
2042
+ specify "should not allow insert, update, delete, or truncate" do
2043
+ proc{@d.join(:categories, :a=>:d).insert_sql}.should raise_error(Sequel::InvalidOperation)
2044
+ proc{@d.join(:categories, :a=>:d).update_sql(:a=>1)}.should raise_error(Sequel::InvalidOperation)
2045
+ proc{@d.join(:categories, :a=>:d).delete_sql}.should raise_error(Sequel::InvalidOperation)
2046
+ proc{@d.join(:categories, :a=>:d).truncate_sql}.should raise_error(Sequel::InvalidOperation)
2047
+ end
2048
+
2049
+ specify "should raise an error if an invalid option is passed" do
2050
+ proc{@d.join(:c, [:id], nil)}.should raise_error(Sequel::Error)
2051
+ proc{@d.join(:c, [:id], :c.qualify(:d))}.should raise_error(Sequel::Error)
2052
+ end
2053
+ end
2054
+
2055
+ context "Dataset#[]=" do
2056
+ before do
2057
+ c = Class.new(Sequel::Dataset) do
2058
+ def last_sql
2059
+ @@last_sql
2060
+ end
2061
+
2062
+ def update(*args)
2063
+ @@last_sql = update_sql(*args)
2064
+ end
2065
+ end
2066
+
2067
+ @d = c.new(nil).from(:items)
2068
+ end
2069
+
2070
+ specify "should perform an update on the specified filter" do
2071
+ @d[:a => 1] = {:x => 3}
2072
+ @d.last_sql.should == 'UPDATE items SET x = 3 WHERE (a = 1)'
2073
+ end
2074
+ end
2075
+
2076
+ context "Dataset#set" do
2077
+ before do
2078
+ c = Class.new(Sequel::Dataset) do
2079
+ def last_sql
2080
+ @@last_sql
2081
+ end
2082
+
2083
+ def update(*args, &block)
2084
+ @@last_sql = update_sql(*args, &block)
2085
+ end
2086
+ end
2087
+
2088
+ @d = c.new(nil).from(:items)
2089
+ end
2090
+
2091
+ specify "should act as alias to #update" do
2092
+ @d.set({:x => 3})
2093
+ @d.last_sql.should == 'UPDATE items SET x = 3'
2094
+ end
2095
+ end
2096
+
2097
+
2098
+ context "Dataset#insert_multiple" do
2099
+ before do
2100
+ c = Class.new(Sequel::Dataset) do
2101
+ attr_reader :inserts
2102
+ def insert(arg)
2103
+ @inserts ||= []
2104
+ @inserts << arg
2105
+ end
2106
+ end
2107
+
2108
+ @d = c.new(nil)
2109
+ end
2110
+
2111
+ specify "should insert all items in the supplied array" do
2112
+ @d.insert_multiple [:aa, 5, 3, {1 => 2}]
2113
+ @d.inserts.should == [:aa, 5, 3, {1 => 2}]
2114
+ end
2115
+
2116
+ specify "should pass array items through the supplied block if given" do
2117
+ a = ["inevitable", "hello", "the ticking clock"]
2118
+ @d.insert_multiple(a) {|i| i.gsub('l', 'r')}
2119
+ @d.inserts.should == ["inevitabre", "herro", "the ticking crock"]
2120
+ end
2121
+ end
2122
+
2123
+ context "Dataset aggregate methods" do
2124
+ before do
2125
+ c = Class.new(Sequel::Dataset) do
2126
+ def fetch_rows(sql)
2127
+ yield({1 => sql})
2128
+ end
2129
+ end
2130
+ @d = c.new(nil).from(:test)
2131
+ end
2132
+
2133
+ specify "should include min" do
2134
+ @d.min(:a).should == 'SELECT min(a) FROM test LIMIT 1'
2135
+ end
2136
+
2137
+ specify "should include max" do
2138
+ @d.max(:b).should == 'SELECT max(b) FROM test LIMIT 1'
2139
+ end
2140
+
2141
+ specify "should include sum" do
2142
+ @d.sum(:c).should == 'SELECT sum(c) FROM test LIMIT 1'
2143
+ end
2144
+
2145
+ specify "should include avg" do
2146
+ @d.avg(:d).should == 'SELECT avg(d) FROM test LIMIT 1'
2147
+ end
2148
+
2149
+ specify "should accept qualified columns" do
2150
+ @d.avg(:test__bc).should == 'SELECT avg(test.bc) FROM test LIMIT 1'
2151
+ end
2152
+
2153
+ specify "should use a subselect for the same conditions as count" do
2154
+ d = @d.order(:a).limit(5)
2155
+ d.avg(:a).should == 'SELECT avg(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
2156
+ d.sum(:a).should == 'SELECT sum(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
2157
+ d.min(:a).should == 'SELECT min(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
2158
+ d.max(:a).should == 'SELECT max(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
2159
+ end
2160
+ end
2161
+
2162
+ context "Dataset#range" do
2163
+ before do
2164
+ c = Class.new(Sequel::Dataset) do
2165
+ @@sql = nil
2166
+
2167
+ def last_sql; @@sql; end
2168
+
2169
+ def fetch_rows(sql)
2170
+ @@sql = sql
2171
+ yield(:v1 => 1, :v2 => 10)
2172
+ end
2173
+ end
2174
+ @d = c.new(nil).from(:test)
2175
+ end
2176
+
2177
+ specify "should generate a correct SQL statement" do
2178
+ @d.range(:stamp)
2179
+ @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test LIMIT 1"
2180
+
2181
+ @d.filter(:price.sql_number > 100).range(:stamp)
2182
+ @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test WHERE (price > 100) LIMIT 1"
2183
+ end
2184
+
2185
+ specify "should return a range object" do
2186
+ @d.range(:tryme).should == (1..10)
2187
+ end
2188
+
2189
+ specify "should use a subselect for the same conditions as count" do
2190
+ @d.order(:stamp).limit(5).range(:stamp).should == (1..10)
2191
+ @d.last_sql.should == 'SELECT min(stamp) AS v1, max(stamp) AS v2 FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1'
2192
+ end
2193
+ end
2194
+
2195
+ context "Dataset#interval" do
2196
+ before do
2197
+ c = Class.new(Sequel::Dataset) do
2198
+ @@sql = nil
2199
+
2200
+ def last_sql; @@sql; end
2201
+
2202
+ def fetch_rows(sql)
2203
+ @@sql = sql
2204
+ yield(:v => 1234)
2205
+ end
2206
+ end
2207
+ @d = c.new(nil).from(:test)
2208
+ end
2209
+
2210
+ specify "should generate a correct SQL statement" do
2211
+ @d.interval(:stamp)
2212
+ @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test LIMIT 1"
2213
+
2214
+ @d.filter(:price.sql_number > 100).interval(:stamp)
2215
+ @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test WHERE (price > 100) LIMIT 1"
2216
+ end
2217
+
2218
+ specify "should return an integer" do
2219
+ @d.interval(:tryme).should == 1234
2220
+ end
2221
+
2222
+ specify "should use a subselect for the same conditions as count" do
2223
+ @d.order(:stamp).limit(5).interval(:stamp).should == 1234
2224
+ @d.last_sql.should == 'SELECT (max(stamp) - min(stamp)) FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1'
2225
+ end
2226
+ end
2227
+
2228
+ context "Dataset #first and #last" do
2229
+ before do
2230
+ @c = Class.new(Sequel::Dataset) do
2231
+ def each(&block)
2232
+ s = select_sql
2233
+ x = [:a,1,:b,2,s]
2234
+ i = /LIMIT (\d+)/.match(s)[1].to_i.times{yield x}
2235
+ end
2236
+ end
2237
+ @d = @c.new(nil).from(:test)
2238
+ end
2239
+
2240
+ specify "should return a single record if no argument is given" do
2241
+ @d.order(:a).first.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a LIMIT 1']
2242
+ @d.order(:a).last.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a DESC LIMIT 1']
2243
+ end
2244
+
2245
+ specify "should return the first/last matching record if argument is not an Integer" do
2246
+ @d.order(:a).first(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a LIMIT 1']
2247
+ @d.order(:a).first('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a LIMIT 1']
2248
+ @d.order(:a).last(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a DESC LIMIT 1']
2249
+ @d.order(:a).last('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a DESC LIMIT 1']
2250
+ end
2251
+
2252
+ specify "should set the limit and return an array of records if the given number is > 1" do
2253
+ i = rand(10) + 10
2254
+ r = @d.order(:a).first(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a LIMIT #{i}"]] * i
2255
+ i = rand(10) + 10
2256
+ r = @d.order(:a).last(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a DESC LIMIT #{i}"]] * i
2257
+ end
2258
+
2259
+ specify "should return the first matching record if a block is given without an argument" do
2260
+ @d.first{:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z > 26) LIMIT 1']
2261
+ @d.order(:name).last{:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z > 26) ORDER BY name DESC LIMIT 1']
2262
+ end
2263
+
2264
+ specify "should combine block and standard argument filters if argument is not an Integer" do
2265
+ @d.first(:y=>25){:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE ((z > 26) AND (y = 25)) LIMIT 1']
2266
+ @d.order(:name).last('y = ?', 16){:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE ((z > 26) AND (y = 16)) ORDER BY name DESC LIMIT 1']
2267
+ end
2268
+
2269
+ specify "should filter and return an array of records if an Integer argument is provided and a block is given" do
2270
+ i = rand(10) + 10
2271
+ r = @d.order(:a).first(i){:z.sql_number > 26}.should == [[:a,1,:b,2, "SELECT * FROM test WHERE (z > 26) ORDER BY a LIMIT #{i}"]] * i
2272
+ i = rand(10) + 10
2273
+ r = @d.order(:a).last(i){:z.sql_number > 26}.should == [[:a,1,:b,2, "SELECT * FROM test WHERE (z > 26) ORDER BY a DESC LIMIT #{i}"]] * i
2274
+ end
2275
+
2276
+ specify "#last should raise if no order is given" do
2277
+ proc {@d.last}.should raise_error(Sequel::Error)
2278
+ proc {@d.last(2)}.should raise_error(Sequel::Error)
2279
+ proc {@d.order(:a).last}.should_not raise_error
2280
+ proc {@d.order(:a).last(2)}.should_not raise_error
2281
+ end
2282
+
2283
+ specify "#last should invert the order" do
2284
+ @d.order(:a).last.pop.should == 'SELECT * FROM test ORDER BY a DESC LIMIT 1'
2285
+ @d.order(:b.desc).last.pop.should == 'SELECT * FROM test ORDER BY b ASC LIMIT 1'
2286
+ @d.order(:c, :d).last.pop.should == 'SELECT * FROM test ORDER BY c DESC, d DESC LIMIT 1'
2287
+ @d.order(:e.desc, :f).last.pop.should == 'SELECT * FROM test ORDER BY e ASC, f DESC LIMIT 1'
2288
+ end
2289
+ end
2290
+
2291
+ context "Dataset compound operations" do
2292
+ before do
2293
+ @a = Sequel::Dataset.new(nil).from(:a).filter(:z => 1)
2294
+ @b = Sequel::Dataset.new(nil).from(:b).filter(:z => 2)
2295
+ end
2296
+
2297
+ specify "should support UNION and UNION ALL" do
2298
+ @a.union(@b).sql.should == \
2299
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1"
2300
+ @b.union(@a, true).sql.should == \
2301
+ "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2302
+ @b.union(@a, :all=>true).sql.should == \
2303
+ "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2304
+ end
2305
+
2306
+ specify "should support INTERSECT and INTERSECT ALL" do
2307
+ @a.intersect(@b).sql.should == \
2308
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS t1"
2309
+ @b.intersect(@a, true).sql.should == \
2310
+ "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2311
+ @b.intersect(@a, :all=>true).sql.should == \
2312
+ "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2313
+ end
2314
+
2315
+ specify "should support EXCEPT and EXCEPT ALL" do
2316
+ @a.except(@b).sql.should == \
2317
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1"
2318
+ @b.except(@a, true).sql.should == \
2319
+ "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2320
+ @b.except(@a, :all=>true).sql.should == \
2321
+ "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2322
+ end
2323
+
2324
+ specify "should support :alias option for specifying identifier" do
2325
+ @a.union(@b, :alias=>:xx).sql.should == \
2326
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS xx"
2327
+ @a.intersect(@b, :alias=>:xx).sql.should == \
2328
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS xx"
2329
+ @a.except(@b, :alias=>:xx).sql.should == \
2330
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS xx"
2331
+ end
2332
+
2333
+ specify "should support :from_self=>false option to not wrap the compound in a SELECT * FROM (...)" do
2334
+ @b.union(@a, :from_self=>false).sql.should == \
2335
+ "SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1)"
2336
+ @b.intersect(@a, :from_self=>false).sql.should == \
2337
+ "SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)"
2338
+ @b.except(@a, :from_self=>false).sql.should == \
2339
+ "SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)"
2340
+
2341
+ @b.union(@a, :from_self=>false, :all=>true).sql.should == \
2342
+ "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
2343
+ @b.intersect(@a, :from_self=>false, :all=>true).sql.should == \
2344
+ "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
2345
+ @b.except(@a, :from_self=>false, :all=>true).sql.should == \
2346
+ "SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
2347
+ end
2348
+
2349
+ specify "should raise an InvalidOperation if INTERSECT or EXCEPT is used and they are not supported" do
2350
+ @a.meta_def(:supports_intersect_except?){false}
2351
+ proc{@a.intersect(@b)}.should raise_error(Sequel::InvalidOperation)
2352
+ proc{@a.intersect(@b, true)}.should raise_error(Sequel::InvalidOperation)
2353
+ proc{@a.except(@b)}.should raise_error(Sequel::InvalidOperation)
2354
+ proc{@a.except(@b, true)}.should raise_error(Sequel::InvalidOperation)
2355
+ end
2356
+
2357
+ specify "should raise an InvalidOperation if INTERSECT ALL or EXCEPT ALL is used and they are not supported" do
2358
+ @a.meta_def(:supports_intersect_except_all?){false}
2359
+ proc{@a.intersect(@b)}.should_not raise_error
2360
+ proc{@a.intersect(@b, true)}.should raise_error(Sequel::InvalidOperation)
2361
+ proc{@a.except(@b)}.should_not raise_error
2362
+ proc{@a.except(@b, true)}.should raise_error(Sequel::InvalidOperation)
2363
+ end
2364
+
2365
+ specify "should handle chained compound operations" do
2366
+ @a.union(@b).union(@a, true).sql.should == \
2367
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1 UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2368
+ @a.intersect(@b, true).intersect(@a).sql.should == \
2369
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM b WHERE (z = 2)) AS t1 INTERSECT SELECT * FROM a WHERE (z = 1)) AS t1"
2370
+ @a.except(@b).except(@a, true).sql.should == \
2371
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1 EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2372
+ end
2373
+
2374
+ specify "should use a subselect when using a compound operation with a dataset that already has a compound operation" do
2375
+ @a.union(@b.union(@a, true)).sql.should == \
2376
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
2377
+ @a.intersect(@b.intersect(@a), true).sql.should == \
2378
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
2379
+ @a.except(@b.except(@a, true)).sql.should == \
2380
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
2381
+ end
2382
+
2383
+ specify "should order and limit properly when using UNION, INTERSECT, or EXCEPT" do
2384
+ @dataset = Sequel::Dataset.new(nil).from(:test)
2385
+ @dataset.union(@dataset).limit(2).sql.should ==
2386
+ "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 2"
2387
+ @dataset.limit(2).intersect(@dataset).sql.should ==
2388
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1 INTERSECT SELECT * FROM test) AS t1"
2389
+ @dataset.except(@dataset.limit(2)).sql.should ==
2390
+ "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1) AS t1"
2391
+
2392
+ @dataset.union(@dataset).order(:num).sql.should ==
2393
+ "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 ORDER BY num"
2394
+ @dataset.order(:num).intersect(@dataset).sql.should ==
2395
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1 INTERSECT SELECT * FROM test) AS t1"
2396
+ @dataset.except(@dataset.order(:num)).sql.should ==
2397
+ "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1) AS t1"
2398
+
2399
+ @dataset.limit(2).order(:a).union(@dataset.limit(3).order(:b)).order(:c).limit(4).sql.should ==
2400
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM test ORDER BY a LIMIT 2) AS t1 UNION SELECT * FROM (SELECT * FROM test ORDER BY b LIMIT 3) AS t1) AS t1 ORDER BY c LIMIT 4"
2401
+ end
2402
+
2403
+ end
2404
+
2405
+ context "Dataset#[]" do
2406
+ before do
2407
+ @c = Class.new(Sequel::Dataset) do
2408
+ @@last_dataset = nil
2409
+
2410
+ def self.last_dataset
2411
+ @@last_dataset
2412
+ end
2413
+
2414
+ def single_record
2415
+ @@last_dataset = opts ? clone(opts) : self
2416
+ {1 => 2, 3 => 4}
2417
+ end
2418
+ end
2419
+ @d = @c.new(nil).from(:test)
2420
+ end
2421
+
2422
+ specify "should return a single record filtered according to the given conditions" do
2423
+ @d[:name => 'didi'].should == {1 => 2, 3 => 4}
2424
+ @c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "(name = 'didi')"
2425
+
2426
+ @d[:id => 5..45].should == {1 => 2, 3 => 4}
2427
+ @c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "((id >= 5) AND (id <= 45))"
2428
+ end
2429
+ end
2430
+
2431
+ context "Dataset#single_record" do
2432
+ before do
2433
+ @c = Class.new(Sequel::Dataset) do
2434
+ def fetch_rows(sql)
2435
+ yield sql
2436
+ end
2437
+ end
2438
+ @cc = Class.new(@c) do
2439
+ def fetch_rows(sql); end
2440
+ end
2441
+
2442
+ @d = @c.new(nil).from(:test)
2443
+ @e = @cc.new(nil).from(:test)
2444
+ end
2445
+
2446
+ specify "should call each with a limit of 1 and return the record" do
2447
+ @d.single_record.should == 'SELECT * FROM test LIMIT 1'
2448
+ end
2449
+
2450
+ specify "should return nil if no record is present" do
2451
+ @e.single_record.should be_nil
2452
+ end
2453
+ end
2454
+
2455
+ context "Dataset#single_value" do
2456
+ before do
2457
+ @c = Class.new(Sequel::Dataset) do
2458
+ def fetch_rows(sql)
2459
+ yield({1 => sql})
2460
+ end
2461
+ end
2462
+ @cc = Class.new(@c) do
2463
+ def fetch_rows(sql); end
2464
+ end
2465
+
2466
+ @d = @c.new(nil).from(:test)
2467
+ @e = @cc.new(nil).from(:test)
2468
+ end
2469
+
2470
+ specify "should call each and return the first value of the first record" do
2471
+ @d.single_value.should == 'SELECT * FROM test LIMIT 1'
2472
+ end
2473
+
2474
+ specify "should return nil if no records" do
2475
+ @e.single_value.should be_nil
2476
+ end
2477
+
2478
+ it "should work on a graphed_dataset" do
2479
+ @d.should_receive(:columns).twice.and_return([:a])
2480
+ @d.graph(@d, [:a], :table_alias=>:test2).single_value.should == 'SELECT test.a, test2.a AS test2_a FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1'
2481
+ end
2482
+ end
2483
+
2484
+ context "Dataset#get" do
2485
+ before do
2486
+ @c = Class.new(Sequel::Dataset) do
2487
+ attr_reader :last_sql
2488
+
2489
+ def fetch_rows(sql)
2490
+ @last_sql = sql
2491
+ yield(:name => sql)
2492
+ end
2493
+ end
2494
+
2495
+ @d = @c.new(nil).from(:test)
2496
+ end
2497
+
2498
+ specify "should select the specified column and fetch its value" do
2499
+ @d.get(:name).should == "SELECT name FROM test LIMIT 1"
2500
+ @d.get(:abc).should == "SELECT abc FROM test LIMIT 1" # the first available value is returned always
2501
+ end
2502
+
2503
+ specify "should work with filters" do
2504
+ @d.filter(:id => 1).get(:name).should == "SELECT name FROM test WHERE (id = 1) LIMIT 1"
2505
+ end
2506
+
2507
+ specify "should work with aliased fields" do
2508
+ @d.get(:x__b.as(:name)).should == "SELECT x.b AS name FROM test LIMIT 1"
2509
+ end
2510
+
2511
+ specify "should accept a block that yields a virtual row" do
2512
+ @d.get{|o| o.x__b.as(:name)}.should == "SELECT x.b AS name FROM test LIMIT 1"
2513
+ @d.get{x(1).as(:name)}.should == "SELECT x(1) AS name FROM test LIMIT 1"
2514
+ end
2515
+
2516
+ specify "should raise an error if both a regular argument and block argument are used" do
2517
+ proc{@d.get(:name){|o| o.x__b.as(:name)}}.should raise_error(Sequel::Error)
2518
+ end
2519
+ end
2520
+
2521
+ context "Dataset#set_row_proc" do
2522
+ before do
2523
+ @c = Class.new(Sequel::Dataset) do
2524
+ def fetch_rows(sql, &block)
2525
+ # yield a hash with kind as the 1 bit of a number
2526
+ (1..10).each {|i| block.call({:kind => i[0]})}
2527
+ end
2528
+ end
2529
+ @dataset = @c.new(nil).from(:items)
2530
+ end
2531
+
2532
+ specify "should cause dataset to pass all rows through the filter" do
2533
+ @dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
2534
+
2535
+ rows = @dataset.all
2536
+ rows.size.should == 10
2537
+
2538
+ rows.each {|r| r[:der].should == (r[:kind] + 2)}
2539
+ end
2540
+
2541
+ specify "should be copied over when dataset is cloned" do
2542
+ @dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
2543
+
2544
+ @dataset.filter(:a => 1).first.should == {:kind => 1, :der => 3}
2545
+ end
2546
+ end
2547
+
2548
+ context "Dataset#<<" do
2549
+ before do
2550
+ @d = Sequel::Dataset.new(nil)
2551
+ @d.meta_def(:insert) do |*args|
2552
+ 1234567890
2553
+ end
2554
+ end
2555
+
2556
+ specify "should call #insert" do
2557
+ (@d << {:name => 1}).should == 1234567890
2558
+ end
2559
+ end
2560
+
2561
+ context "Dataset#columns" do
2562
+ before do
2563
+ @dataset = DummyDataset.new(nil).from(:items)
2564
+ @dataset.meta_def(:columns=) {|c| @columns = c}
2565
+ i = 'a'
2566
+ @dataset.meta_def(:each){@columns = select_sql + i; i = i.next}
2567
+ end
2568
+
2569
+ specify "should return the value of @columns if @columns is not nil" do
2570
+ @dataset.columns = [:a, :b, :c]
2571
+ @dataset.columns.should == [:a, :b, :c]
2572
+ end
2573
+
2574
+ specify "should attempt to get a single record and return @columns if @columns is nil" do
2575
+ @dataset.columns = nil
2576
+ @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2577
+ @dataset.opts[:from] = [:nana]
2578
+ @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2579
+ end
2580
+
2581
+ specify "should ignore any filters, orders, or DISTINCT clauses" do
2582
+ @dataset.filter!(:b=>100).order!(:b).distinct!
2583
+ @dataset.columns = nil
2584
+ @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2585
+ end
2586
+ end
2587
+
2588
+ context "Dataset#columns!" do
2589
+ before do
2590
+ @dataset = DummyDataset.new(nil).from(:items)
2591
+ i = 'a'
2592
+ @dataset.meta_def(:each){@columns = select_sql + i; i = i.next}
2593
+ end
2594
+
2595
+ specify "should always attempt to get a record and return @columns" do
2596
+ @dataset.columns!.should == 'SELECT * FROM items LIMIT 1a'
2597
+ @dataset.columns!.should == 'SELECT * FROM items LIMIT 1b'
2598
+ @dataset.opts[:from] = [:nana]
2599
+ @dataset.columns!.should == 'SELECT * FROM nana LIMIT 1c'
2600
+ end
2601
+ end
2602
+
2603
+ context "Dataset#import" do
2604
+ before do
2605
+ @dbc = Class.new(Sequel::Database) do
2606
+ attr_reader :sqls
2607
+
2608
+ def execute(sql, opts={})
2609
+ @sqls ||= []
2610
+ @sqls << sql
2611
+ end
2612
+ alias execute_dui execute
2613
+
2614
+ def transaction(opts={})
2615
+ @sqls ||= []
2616
+ @sqls << 'BEGIN'
2617
+ yield
2618
+ @sqls << 'COMMIT'
2619
+ end
2620
+ end
2621
+ @db = @dbc.new
2622
+
2623
+ @ds = Sequel::Dataset.new(@db).from(:items)
2624
+
2625
+ @list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
2626
+ end
2627
+
2628
+ specify "should accept string keys as column names" do
2629
+ @ds.import(['x', 'y'], [[1, 2], [3, 4]])
2630
+ @db.sqls.should == [
2631
+ 'BEGIN',
2632
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2633
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2634
+ 'COMMIT'
2635
+ ]
2636
+ end
2637
+
2638
+ specify "should accept a columns array and a values array" do
2639
+ @ds.import([:x, :y], [[1, 2], [3, 4]])
2640
+ @db.sqls.should == [
2641
+ 'BEGIN',
2642
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2643
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2644
+ 'COMMIT'
2645
+ ]
2646
+ end
2647
+
2648
+ specify "should accept a columns array and a dataset" do
2649
+ @ds2 = Sequel::Dataset.new(@db).from(:cats).filter(:purr => true).select(:a, :b)
2650
+
2651
+ @ds.import([:x, :y], @ds2)
2652
+ @db.sqls.should == [
2653
+ 'BEGIN',
2654
+ "INSERT INTO items (x, y) SELECT a, b FROM cats WHERE (purr IS TRUE)",
2655
+ 'COMMIT'
2656
+ ]
2657
+ end
2658
+
2659
+ specify "should accept a columns array and a values array with :commit_every option" do
2660
+ @ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :commit_every => 3)
2661
+ @db.sqls.should == [
2662
+ 'BEGIN',
2663
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2664
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2665
+ "INSERT INTO items (x, y) VALUES (5, 6)",
2666
+ 'COMMIT',
2667
+ ]
2668
+ end
2669
+ specify "should accept a columns array and a values array with slice option" do
2670
+ @ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
2671
+ @db.sqls.should == [
2672
+ 'BEGIN',
2673
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2674
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2675
+ 'COMMIT',
2676
+ 'BEGIN',
2677
+ "INSERT INTO items (x, y) VALUES (5, 6)",
2678
+ 'COMMIT'
2679
+ ]
2680
+ end
2681
+ end
2682
+
2683
+ context "Dataset#multi_insert" do
2684
+ before do
2685
+ @dbc = Class.new do
2686
+ attr_reader :sqls
2687
+
2688
+ def execute(sql, opts={})
2689
+ @sqls ||= []
2690
+ @sqls << sql
2691
+ end
2692
+ alias execute_dui execute
2693
+
2694
+ def transaction(opts={})
2695
+ @sqls ||= []
2696
+ @sqls << 'BEGIN'
2697
+ yield
2698
+ @sqls << 'COMMIT'
2699
+ end
2700
+ end
2701
+ @db = @dbc.new
2702
+
2703
+ @ds = Sequel::Dataset.new(@db).from(:items)
2704
+
2705
+ @list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
2706
+ end
2707
+
2708
+ specify "should issue multiple insert statements inside a transaction" do
2709
+ @ds.multi_insert(@list)
2710
+ @db.sqls.should == [
2711
+ 'BEGIN',
2712
+ "INSERT INTO items (name) VALUES ('abc')",
2713
+ "INSERT INTO items (name) VALUES ('def')",
2714
+ "INSERT INTO items (name) VALUES ('ghi')",
2715
+ 'COMMIT'
2716
+ ]
2717
+ end
2718
+
2719
+ specify "should handle different formats for tables" do
2720
+ @ds = @ds.from(:sch__tab)
2721
+ @ds.multi_insert(@list)
2722
+ @db.sqls.should == [
2723
+ 'BEGIN',
2724
+ "INSERT INTO sch.tab (name) VALUES ('abc')",
2725
+ "INSERT INTO sch.tab (name) VALUES ('def')",
2726
+ "INSERT INTO sch.tab (name) VALUES ('ghi')",
2727
+ 'COMMIT'
2728
+ ]
2729
+ @db.sqls.clear
2730
+
2731
+ @ds = @ds.from(:tab.qualify(:sch))
2732
+ @ds.multi_insert(@list)
2733
+ @db.sqls.should == [
2734
+ 'BEGIN',
2735
+ "INSERT INTO sch.tab (name) VALUES ('abc')",
2736
+ "INSERT INTO sch.tab (name) VALUES ('def')",
2737
+ "INSERT INTO sch.tab (name) VALUES ('ghi')",
2738
+ 'COMMIT'
2739
+ ]
2740
+ @db.sqls.clear
2741
+ @ds = @ds.from(:sch__tab.identifier)
2742
+ @ds.multi_insert(@list)
2743
+ @db.sqls.should == [
2744
+ 'BEGIN',
2745
+ "INSERT INTO sch__tab (name) VALUES ('abc')",
2746
+ "INSERT INTO sch__tab (name) VALUES ('def')",
2747
+ "INSERT INTO sch__tab (name) VALUES ('ghi')",
2748
+ 'COMMIT'
2749
+ ]
2750
+ end
2751
+
2752
+ specify "should accept the :commit_every option for committing every x records" do
2753
+ @ds.multi_insert(@list, :commit_every => 1)
2754
+ @db.sqls.should == [
2755
+ 'BEGIN',
2756
+ "INSERT INTO items (name) VALUES ('abc')",
2757
+ 'COMMIT',
2758
+ 'BEGIN',
2759
+ "INSERT INTO items (name) VALUES ('def')",
2760
+ 'COMMIT',
2761
+ 'BEGIN',
2762
+ "INSERT INTO items (name) VALUES ('ghi')",
2763
+ 'COMMIT'
2764
+ ]
2765
+ end
2766
+
2767
+ specify "should accept the :slice option for committing every x records" do
2768
+ @ds.multi_insert(@list, :slice => 2)
2769
+ @db.sqls.should == [
2770
+ 'BEGIN',
2771
+ "INSERT INTO items (name) VALUES ('abc')",
2772
+ "INSERT INTO items (name) VALUES ('def')",
2773
+ 'COMMIT',
2774
+ 'BEGIN',
2775
+ "INSERT INTO items (name) VALUES ('ghi')",
2776
+ 'COMMIT'
2777
+ ]
2778
+ end
2779
+
2780
+ specify "should accept string keys as column names" do
2781
+ @ds.multi_insert([{'x'=>1, 'y'=>2}, {'x'=>3, 'y'=>4}])
2782
+ @db.sqls.should == [
2783
+ 'BEGIN',
2784
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2785
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2786
+ 'COMMIT'
2787
+ ]
2788
+ end
2789
+
2790
+ specify "should not do anything if no hashes are provided" do
2791
+ @ds.multi_insert([])
2792
+ @db.sqls.should be_nil
2793
+ end
2794
+ end
2795
+
2796
+ context "Dataset" do
2797
+ before do
2798
+ @d = Sequel::Dataset.new(nil).from(:x)
2799
+ end
2800
+
2801
+ specify "should support self-changing select!" do
2802
+ @d.select!(:y)
2803
+ @d.sql.should == "SELECT y FROM x"
2804
+ end
2805
+
2806
+ specify "should support self-changing from!" do
2807
+ @d.from!(:y)
2808
+ @d.sql.should == "SELECT * FROM y"
2809
+ end
2810
+
2811
+ specify "should support self-changing order!" do
2812
+ @d.order!(:y)
2813
+ @d.sql.should == "SELECT * FROM x ORDER BY y"
2814
+ end
2815
+
2816
+ specify "should support self-changing filter!" do
2817
+ @d.filter!(:y => 1)
2818
+ @d.sql.should == "SELECT * FROM x WHERE (y = 1)"
2819
+ end
2820
+
2821
+ specify "should support self-changing filter! with block" do
2822
+ @d.filter!{:y.sql_number < 2}
2823
+ @d.sql.should == "SELECT * FROM x WHERE (y < 2)"
2824
+ end
2825
+
2826
+ specify "should raise for ! methods that don't return a dataset" do
2827
+ proc {@d.opts!}.should raise_error(NameError)
2828
+ end
2829
+
2830
+ specify "should raise for missing methods" do
2831
+ proc {@d.xuyz}.should raise_error(NameError)
2832
+ proc {@d.xyz!}.should raise_error(NameError)
2833
+ proc {@d.xyz?}.should raise_error(NameError)
2834
+ end
2835
+
2836
+ specify "should support chaining of bang methods" do
2837
+ @d.order!(:y)
2838
+ @d.filter!(:y => 1)
2839
+ @d.sql.should == "SELECT * FROM x WHERE (y = 1) ORDER BY y"
2840
+ end
2841
+ end
2842
+
2843
+ context "Dataset#to_csv" do
2844
+ before do
2845
+ @c = Class.new(Sequel::Dataset) do
2846
+ attr_accessor :data
2847
+ attr_accessor :columns
2848
+
2849
+ def fetch_rows(sql, &block)
2850
+ @data.each(&block)
2851
+ end
2852
+
2853
+ # naked should return self here because to_csv wants a naked result set.
2854
+ def naked
2855
+ self
2856
+ end
2857
+ end
2858
+
2859
+ @ds = @c.new(nil).from(:items)
2860
+ @ds.columns = [:a, :b, :c]
2861
+ @ds.data = [ {:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}, {:a=>7, :b=>8, :c=>9} ]
2862
+ end
2863
+
2864
+ specify "should format a CSV representation of the records" do
2865
+ @ds.to_csv.should ==
2866
+ "a, b, c\r\n1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2867
+ end
2868
+
2869
+ specify "should exclude column titles if so specified" do
2870
+ @ds.to_csv(false).should ==
2871
+ "1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2872
+ end
2873
+ end
2874
+
2875
+ context "Dataset#update_sql" do
2876
+ before do
2877
+ @ds = Sequel::Dataset.new(nil).from(:items)
2878
+ end
2879
+
2880
+ specify "should accept strings" do
2881
+ @ds.update_sql("a = b").should == "UPDATE items SET a = b"
2882
+ end
2883
+
2884
+ specify "should handle implicitly qualified symbols" do
2885
+ @ds.update_sql(:items__a=>:b).should == "UPDATE items SET items.a = b"
2886
+ end
2887
+
2888
+ specify "should accept hash with string keys" do
2889
+ @ds.update_sql('c' => 'd').should == "UPDATE items SET c = 'd'"
2890
+ end
2891
+
2892
+ specify "should accept array subscript references" do
2893
+ @ds.update_sql((:day.sql_subscript(1)) => 'd').should == "UPDATE items SET day[1] = 'd'"
2894
+ end
2895
+ end
2896
+
2897
+ context "Dataset#insert_sql" do
2898
+ before do
2899
+ @ds = Sequel::Dataset.new(nil).from(:items)
2900
+ end
2901
+
2902
+ specify "should accept hash with symbol keys" do
2903
+ @ds.insert_sql(:c => 'd').should == "INSERT INTO items (c) VALUES ('d')"
2904
+ end
2905
+
2906
+ specify "should accept hash with string keys" do
2907
+ @ds.insert_sql('c' => 'd').should == "INSERT INTO items (c) VALUES ('d')"
2908
+ end
2909
+
2910
+ specify "should accept array subscript references" do
2911
+ @ds.insert_sql((:day.sql_subscript(1)) => 'd').should == "INSERT INTO items (day[1]) VALUES ('d')"
2912
+ end
2913
+
2914
+ specify "should raise an Error if the dataset has no sources" do
2915
+ proc{Sequel::Database.new.dataset.insert_sql}.should raise_error(Sequel::Error)
2916
+ end
2917
+
2918
+ specify "should accept datasets" do
2919
+ @ds.insert_sql(@ds).should == "INSERT INTO items SELECT * FROM items"
2920
+ end
2921
+
2922
+ specify "should accept datasets with columns" do
2923
+ @ds.insert_sql([:a, :b], @ds).should == "INSERT INTO items (a, b) SELECT * FROM items"
2924
+ end
2925
+
2926
+ specify "should raise if given bad values" do
2927
+ proc{@ds.clone(:values=>'a').send(:_insert_sql)}.should raise_error(Sequel::Error)
2928
+ end
2929
+
2930
+ specify "should accept separate values" do
2931
+ @ds.insert_sql(1).should == "INSERT INTO items VALUES (1)"
2932
+ @ds.insert_sql(1, 2).should == "INSERT INTO items VALUES (1, 2)"
2933
+ @ds.insert_sql(1, 2, 3).should == "INSERT INTO items VALUES (1, 2, 3)"
2934
+ end
2935
+
2936
+ specify "should accept a single array of values" do
2937
+ @ds.insert_sql([1, 2, 3]).should == "INSERT INTO items VALUES (1, 2, 3)"
2938
+ end
2939
+
2940
+ specify "should accept an array of columns and an array of values" do
2941
+ @ds.insert_sql([:a, :b, :c], [1, 2, 3]).should == "INSERT INTO items (a, b, c) VALUES (1, 2, 3)"
2942
+ end
2943
+
2944
+ specify "should raise an array if the columns and values differ in size" do
2945
+ proc{@ds.insert_sql([:a, :b], [1, 2, 3])}.should raise_error(Sequel::Error)
2946
+ end
2947
+
2948
+ specify "should accept a single LiteralString" do
2949
+ @ds.insert_sql('VALUES (1, 2, 3)'.lit).should == "INSERT INTO items VALUES (1, 2, 3)"
2950
+ end
2951
+
2952
+ specify "should accept an array of columns and an LiteralString" do
2953
+ @ds.insert_sql([:a, :b, :c], 'VALUES (1, 2, 3)'.lit).should == "INSERT INTO items (a, b, c) VALUES (1, 2, 3)"
2954
+ end
2955
+
2956
+ specify "should accept an object that responds to values and returns a hash by using that hash as the columns and values" do
2957
+ o = Object.new
2958
+ def o.values; {:c=>'d'}; end
2959
+ @ds.insert_sql(o).should == "INSERT INTO items (c) VALUES ('d')"
2960
+ end
2961
+
2962
+ specify "should accept an object that responds to values and returns something other than a hash by using the object itself as a single value" do
2963
+ o = Date.civil(2000, 1, 1)
2964
+ def o.values; self; end
2965
+ @ds.insert_sql(o).should == "INSERT INTO items VALUES ('2000-01-01')"
2966
+ end
2967
+ end
2968
+
2969
+ class DummyMummyDataset < Sequel::Dataset
2970
+ def first
2971
+ raise if @opts[:from] == [:a]
2972
+ true
2973
+ end
2974
+ end
2975
+
2976
+ class DummyMummyDatabase < Sequel::Database
2977
+ attr_reader :sqls
2978
+
2979
+ def execute(sql)
2980
+ @sqls ||= []
2981
+ @sqls << sql
2982
+ end
2983
+
2984
+ def transaction; yield; end
2985
+
2986
+ def dataset
2987
+ DummyMummyDataset.new(self)
2988
+ end
2989
+ end
2990
+
2991
+ context "Dataset#inspect" do
2992
+ before do
2993
+ @ds = Sequel::Dataset.new(nil).from(:blah)
2994
+ end
2995
+
2996
+ specify "should include the class name and the corresponding SQL statement" do
2997
+ @ds.inspect.should == '#<%s: %s>' % [@ds.class.to_s, @ds.sql.inspect]
2998
+ end
2999
+ end
3000
+
3001
+ context "Dataset#all" do
3002
+ before do
3003
+ @c = Class.new(Sequel::Dataset) do
3004
+ def fetch_rows(sql, &block)
3005
+ block.call({:x => 1, :y => 2})
3006
+ block.call({:x => 3, :y => 4})
3007
+ block.call(sql)
3008
+ end
3009
+ end
3010
+ @dataset = @c.new(nil).from(:items)
3011
+ end
3012
+
3013
+ specify "should return an array with all records" do
3014
+ @dataset.all.should == [
3015
+ {:x => 1, :y => 2},
3016
+ {:x => 3, :y => 4},
3017
+ "SELECT * FROM items"
3018
+ ]
3019
+ end
3020
+
3021
+ specify "should iterate over the array if a block is given" do
3022
+ a = []
3023
+
3024
+ @dataset.all do |r|
3025
+ a << (r.is_a?(Hash) ? r[:x] : r)
3026
+ end
3027
+
3028
+ a.should == [1, 3, "SELECT * FROM items"]
3029
+ end
3030
+ end
3031
+
3032
+ context "Dataset#grep" do
3033
+ before do
3034
+ @ds = Sequel::Dataset.new(nil).from(:posts)
3035
+ end
3036
+
3037
+ specify "should format a SQL filter correctly" do
3038
+ @ds.grep(:title, 'ruby').sql.should ==
3039
+ "SELECT * FROM posts WHERE ((title LIKE 'ruby'))"
3040
+ end
3041
+
3042
+ specify "should support multiple columns" do
3043
+ @ds.grep([:title, :body], 'ruby').sql.should ==
3044
+ "SELECT * FROM posts WHERE ((title LIKE 'ruby') OR (body LIKE 'ruby'))"
3045
+ end
3046
+
3047
+ specify "should support multiple search terms" do
3048
+ @ds.grep(:title, ['abc', 'def']).sql.should ==
3049
+ "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def'))"
3050
+ end
3051
+
3052
+ specify "should support multiple columns and search terms" do
3053
+ @ds.grep([:title, :body], ['abc', 'def']).sql.should ==
3054
+ "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def') OR (body LIKE 'abc') OR (body LIKE 'def'))"
3055
+ end
3056
+
3057
+ specify "should support regexps though the database may not support it" do
3058
+ @ds.grep(:title, /ruby/).sql.should ==
3059
+ "SELECT * FROM posts WHERE ((title ~ 'ruby'))"
3060
+
3061
+ @ds.grep(:title, [/^ruby/, 'ruby']).sql.should ==
3062
+ "SELECT * FROM posts WHERE ((title ~ '^ruby') OR (title LIKE 'ruby'))"
3063
+ end
3064
+
3065
+ specify "should support searching against other columns" do
3066
+ @ds.grep(:title, :body).sql.should ==
3067
+ "SELECT * FROM posts WHERE ((title LIKE body))"
3068
+ end
3069
+ end
3070
+
3071
+ context "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #execute" do
3072
+ before do
3073
+ @db = Sequel::Database.new
3074
+ @ds = @db[:items]
3075
+ end
3076
+
3077
+ specify "#fetch_rows should raise a NotImplementedError" do
3078
+ proc{@ds.fetch_rows(''){}}.should raise_error(NotImplementedError)
3079
+ end
3080
+
3081
+ specify "#delete should execute delete SQL" do
3082
+ @db.should_receive(:execute).once.with('DELETE FROM items', :server=>:default)
3083
+ @ds.delete
3084
+ @db.should_receive(:execute_dui).once.with('DELETE FROM items', :server=>:default)
3085
+ @ds.delete
3086
+ end
3087
+
3088
+ specify "#insert should execute insert SQL" do
3089
+ @db.should_receive(:execute).once.with('INSERT INTO items DEFAULT VALUES', :server=>:default)
3090
+ @ds.insert([])
3091
+ @db.should_receive(:execute_insert).once.with('INSERT INTO items DEFAULT VALUES', :server=>:default)
3092
+ @ds.insert([])
3093
+ end
3094
+
3095
+ specify "#update should execute update SQL" do
3096
+ @db.should_receive(:execute).once.with('UPDATE items SET number = 1', :server=>:default)
3097
+ @ds.update(:number=>1)
3098
+ @db.should_receive(:execute_dui).once.with('UPDATE items SET number = 1', :server=>:default)
3099
+ @ds.update(:number=>1)
3100
+ end
3101
+
3102
+ specify "#truncate should execute truncate SQL" do
3103
+ @db.should_receive(:execute).once.with('TRUNCATE TABLE items', :server=>:default)
3104
+ @ds.truncate.should == nil
3105
+ @db.should_receive(:execute_ddl).once.with('TRUNCATE TABLE items', :server=>:default)
3106
+ @ds.truncate.should == nil
3107
+ end
3108
+
3109
+ specify "#truncate should raise an InvalidOperation exception if the dataset is filtered" do
3110
+ proc{@ds.filter(:a=>1).truncate}.should raise_error(Sequel::InvalidOperation)
3111
+ end
3112
+
3113
+ specify "#execute should execute the SQL on the database" do
3114
+ @db.should_receive(:execute).once.with('SELECT 1', :server=>:read_only)
3115
+ @ds.send(:execute, 'SELECT 1')
3116
+ end
3117
+ end
3118
+
3119
+ context "Dataset prepared statements and bound variables " do
3120
+ before do
3121
+ @db = Sequel::Database.new
3122
+ @db.meta_def(:sqls){@sqls||=[]}
3123
+ def @db.execute(sql, opts={})
3124
+ sqls << sql
3125
+ end
3126
+ def @db.dataset
3127
+ ds = super()
3128
+ def ds.fetch_rows(sql, &block)
3129
+ execute(sql)
3130
+ end
3131
+ ds
3132
+ end
3133
+ @ds = @db[:items]
3134
+ end
3135
+
3136
+ specify "#call should take a type and bind hash and interpolate it" do
3137
+ @ds.filter(:num=>:$n).call(:select, :n=>1)
3138
+ @ds.filter(:num=>:$n).call(:first, :n=>1)
3139
+ @ds.filter(:num=>:$n).call(:delete, :n=>1)
3140
+ @ds.filter(:num=>:$n).call(:update, {:n=>1, :n2=>2}, :num=>:$n2)
3141
+ @ds.call(:insert, {:n=>1}, :num=>:$n)
3142
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3143
+ 'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3144
+ 'DELETE FROM items WHERE (num = 1)',
3145
+ 'UPDATE items SET num = 2 WHERE (num = 1)',
3146
+ 'INSERT INTO items (num) VALUES (1)']
3147
+ end
3148
+
3149
+ specify "#prepare should take a type and name and store it in the database for later use with call" do
3150
+ pss = []
3151
+ pss << @ds.filter(:num=>:$n).prepare(:select, :sn)
3152
+ pss << @ds.filter(:num=>:$n).prepare(:first, :fn)
3153
+ pss << @ds.filter(:num=>:$n).prepare(:delete, :dn)
3154
+ pss << @ds.filter(:num=>:$n).prepare(:update, :un, :num=>:$n2)
3155
+ pss << @ds.prepare(:insert, :in, :num=>:$n)
3156
+ @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :sn, :un]
3157
+ [:sn, :fn, :dn, :un, :in].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3158
+ @db.call(:sn, :n=>1)
3159
+ @db.call(:fn, :n=>1)
3160
+ @db.call(:dn, :n=>1)
3161
+ @db.call(:un, :n=>1, :n2=>2)
3162
+ @db.call(:in, :n=>1)
3163
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3164
+ 'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3165
+ 'DELETE FROM items WHERE (num = 1)',
3166
+ 'UPDATE items SET num = 2 WHERE (num = 1)',
3167
+ 'INSERT INTO items (num) VALUES (1)']
3168
+ end
3169
+
3170
+ specify "#inspect should indicate it is a prepared statement with the prepared SQL" do
3171
+ @ds.filter(:num=>:$n).prepare(:select, :sn).inspect.should == \
3172
+ '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
3173
+ end
3174
+
3175
+ specify "should handle literal strings" do
3176
+ @ds.filter("num = ?", :$n).call(:select, :n=>1)
3177
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3178
+ end
3179
+
3180
+ specify "should handle datasets using static sql and placeholders" do
3181
+ @db["SELECT * FROM items WHERE (num = ?)", :$n].call(:select, :n=>1)
3182
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3183
+ end
3184
+
3185
+ specify "should handle subselects" do
3186
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter(:num=>:$n)).filter(:$c).call(:select, :n=>1, :b=>0, :c=>2)
3187
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))) AND 2)']
3188
+ end
3189
+
3190
+ specify "should handle subselects in subselects" do
3191
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter(:num=>@ds.select(:num).filter(:num=>:$n))).call(:select, :n=>1, :b=>0)
3192
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num IN (SELECT num FROM items WHERE (num = 1))))))']
3193
+ end
3194
+
3195
+ specify "should handle subselects with literal strings" do
3196
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter("num = ?", :$n)).call(:select, :n=>1, :b=>0)
3197
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))))']
3198
+ end
3199
+
3200
+ specify "should handle subselects with static sql and placeholders" do
3201
+ @ds.filter(:$b).filter(:num=>@db["SELECT num FROM items WHERE (num = ?)", :$n]).call(:select, :n=>1, :b=>0)
3202
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))))']
3203
+ end
3204
+ end
3205
+
3206
+ context Sequel::Dataset::UnnumberedArgumentMapper do
3207
+ before do
3208
+ @db = Sequel::Database.new
3209
+ @db.meta_def(:sqls){@sqls||=[]}
3210
+ def @db.execute(sql, opts={})
3211
+ sqls << [sql, *opts[:arguments]]
3212
+ end
3213
+ @ds = @db[:items].filter(:num=>:$n)
3214
+ def @ds.fetch_rows(sql, &block)
3215
+ execute(sql)
3216
+ end
3217
+ def @ds.execute(sql, opts={}, &block)
3218
+ @db.execute(sql, {:arguments=>bind_arguments}.merge(opts))
3219
+ end
3220
+ def @ds.execute_dui(*args, &block)
3221
+ execute(*args, &block)
3222
+ end
3223
+ def @ds.execute_insert(*args, &block)
3224
+ execute(*args, &block)
3225
+ end
3226
+ @ps = []
3227
+ @ps << @ds.prepare(:select, :s)
3228
+ @ps << @ds.prepare(:all, :a)
3229
+ @ps << @ds.prepare(:first, :f)
3230
+ @ps << @ds.prepare(:delete, :d)
3231
+ @ps << @ds.prepare(:insert, :i, :num=>:$n)
3232
+ @ps << @ds.prepare(:update, :u, :num=>:$n)
3233
+ @ps.each{|p| p.extend(Sequel::Dataset::UnnumberedArgumentMapper)}
3234
+ end
3235
+
3236
+ specify "#inspect should show the actual SQL submitted to the database" do
3237
+ @ps.first.inspect.should == '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
3238
+ end
3239
+
3240
+ specify "should submitted the SQL to the database with placeholders and bind variables" do
3241
+ @ps.each{|p| p.prepared_sql; p.call(:n=>1)}
3242
+ @db.sqls.should == [["SELECT * FROM items WHERE (num = ?)", 1],
3243
+ ["SELECT * FROM items WHERE (num = ?)", 1],
3244
+ ["SELECT * FROM items WHERE (num = ?) LIMIT 1", 1],
3245
+ ["DELETE FROM items WHERE (num = ?)", 1],
3246
+ ["INSERT INTO items (num) VALUES (?)", 1],
3247
+ ["UPDATE items SET num = ? WHERE (num = ?)", 1, 1],
3248
+ ]
3249
+ end
3250
+ end
3251
+
3252
+ context "Sequel::Dataset#server" do
3253
+ specify "should set the server to use for the dataset" do
3254
+ @db = Sequel::Database.new
3255
+ @ds = @db[:items].server(:s)
3256
+ sqls = []
3257
+ @db.meta_def(:execute) do |sql, opts|
3258
+ sqls << [sql, opts[:server]]
3259
+ end
3260
+ def @ds.fetch_rows(sql, &block)
3261
+ execute(sql)
3262
+ end
3263
+ @ds.all
3264
+ @ds.server(:i).insert(:a=>1)
3265
+ @ds.server(:d).delete
3266
+ @ds.server(:u).update(:a=>:a+1)
3267
+ sqls.should == [['SELECT * FROM items', :s],
3268
+ ['INSERT INTO items (a) VALUES (1)', :i],
3269
+ ['DELETE FROM items', :d],
3270
+ ['UPDATE items SET a = (a + 1)', :u]]
3271
+ end
3272
+ end
3273
+
3274
+ context "Sequel::Dataset#each_server" do
3275
+ before do
3276
+ @db = Sequel::Database.new(:servers=>{:s=>{}, :i=>{}})
3277
+ @ds = @db[:items]
3278
+ sqls = @sqls = []
3279
+ @db.meta_def(:execute) do |sql, opts|
3280
+ sqls << [sql, opts[:server]]
3281
+ end
3282
+ def @ds.fetch_rows(sql, &block)
3283
+ execute(sql)
3284
+ end
3285
+ end
3286
+
3287
+ specify "should yield a dataset for each server" do
3288
+ @ds.each_server do |ds|
3289
+ ds.should be_a_kind_of(Sequel::Dataset)
3290
+ ds.should_not == @ds
3291
+ ds.sql.should == @ds.sql
3292
+ ds.all
3293
+ end
3294
+ @sqls.sort_by{|sql, s| s.to_s}.should == [['SELECT * FROM items', :default], ['SELECT * FROM items', :i], ['SELECT * FROM items', :s]]
3295
+ end
3296
+ end
3297
+
3298
+ context "Sequel::Dataset #set_defaults" do
3299
+ before do
3300
+ @ds = Sequel::Dataset.new(nil).from(:items).set_defaults(:x=>1)
3301
+ end
3302
+
3303
+ specify "should set the default values for inserts" do
3304
+ @ds.insert_sql.should == "INSERT INTO items (x) VALUES (1)"
3305
+ @ds.insert_sql(:x=>2).should == "INSERT INTO items (x) VALUES (2)"
3306
+ @ds.insert_sql(:y=>2).should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3307
+ @ds.set_defaults(:y=>2).insert_sql.should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3308
+ @ds.set_defaults(:x=>2).insert_sql.should == "INSERT INTO items (x) VALUES (2)"
3309
+ end
3310
+
3311
+ specify "should set the default values for updates" do
3312
+ @ds.update_sql.should == "UPDATE items SET x = 1"
3313
+ @ds.update_sql(:x=>2).should == "UPDATE items SET x = 2"
3314
+ @ds.update_sql(:y=>2).should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3315
+ @ds.set_defaults(:y=>2).update_sql.should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3316
+ @ds.set_defaults(:x=>2).update_sql.should == "UPDATE items SET x = 2"
3317
+ end
3318
+ end
3319
+
3320
+ context "Sequel::Dataset #set_overrides" do
3321
+ before do
3322
+ @ds = Sequel::Dataset.new(nil).from(:items).set_overrides(:x=>1)
3323
+ end
3324
+
3325
+ specify "should override the given values for inserts" do
3326
+ @ds.insert_sql.should == "INSERT INTO items (x) VALUES (1)"
3327
+ @ds.insert_sql(:x=>2).should == "INSERT INTO items (x) VALUES (1)"
3328
+ @ds.insert_sql(:y=>2).should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3329
+ @ds.set_overrides(:y=>2).insert_sql.should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3330
+ @ds.set_overrides(:x=>2).insert_sql.should == "INSERT INTO items (x) VALUES (1)"
3331
+ end
3332
+
3333
+ specify "should override the given values for updates" do
3334
+ @ds.update_sql.should == "UPDATE items SET x = 1"
3335
+ @ds.update_sql(:x=>2).should == "UPDATE items SET x = 1"
3336
+ @ds.update_sql(:y=>2).should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3337
+ @ds.set_overrides(:y=>2).update_sql.should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3338
+ @ds.set_overrides(:x=>2).update_sql.should == "UPDATE items SET x = 1"
3339
+ end
3340
+ end
3341
+
3342
+ context "Sequel::Dataset#qualify" do
3343
+ specify "should qualify to the given table" do
3344
+ MockDatabase.new[:t].filter{a<b}.qualify(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3345
+ end
3346
+
3347
+ specify "should qualify to the first source if no table if given" do
3348
+ MockDatabase.new[:t].filter{a<b}.qualify.sql.should == 'SELECT t.* FROM t WHERE (t.a < t.b)'
3349
+ end
3350
+ end
3351
+
3352
+ context "Sequel::Dataset#qualify_to" do
3353
+ specify "should qualify to the given table" do
3354
+ MockDatabase.new[:t].filter{a<b}.qualify_to(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3355
+ end
3356
+ end
3357
+
3358
+ context "Sequel::Dataset#qualify_to_first_source" do
3359
+ before do
3360
+ @ds = MockDatabase.new[:t]
3361
+ end
3362
+
3363
+ specify "should qualify_to the first source" do
3364
+ @ds.qualify_to_first_source.sql.should == 'SELECT t.* FROM t'
3365
+ @ds.should_receive(:qualify_to).with(:t).once
3366
+ @ds.qualify_to_first_source
3367
+ end
3368
+
3369
+ specify "should handle the select, order, where, having, and group options/clauses" do
3370
+ @ds.select(:a).filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == \
3371
+ 'SELECT t.a FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
3372
+ end
3373
+
3374
+ specify "should handle the select using a table.* if all columns are currently selected" do
3375
+ @ds.filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == \
3376
+ 'SELECT t.* FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
3377
+ end
3378
+
3379
+ specify "should handle hashes in select option" do
3380
+ @ds.select(:a=>:b).qualify_to_first_source.sql.should == 'SELECT t.a AS b FROM t'
3381
+ end
3382
+
3383
+ specify "should handle symbols" do
3384
+ @ds.select(:a, :b__c, :d___e, :f__g___h).qualify_to_first_source.sql.should == 'SELECT t.a, b.c, t.d AS e, f.g AS h FROM t'
3385
+ end
3386
+
3387
+ specify "should handle arrays" do
3388
+ @ds.filter(:a=>[:b, :c]).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.a IN (t.b, t.c))'
3389
+ end
3390
+
3391
+ specify "should handle hashes" do
3392
+ @ds.select({:b=>{:c=>1}}.case(false)).qualify_to_first_source.sql.should == "SELECT (CASE WHEN t.b THEN (t.c = 1) ELSE 'f' END) FROM t"
3393
+ end
3394
+
3395
+ specify "should handle SQL::Identifiers" do
3396
+ @ds.select{a}.qualify_to_first_source.sql.should == 'SELECT t.a FROM t'
3397
+ end
3398
+
3399
+ specify "should handle SQL::OrderedExpressions" do
3400
+ @ds.order(:a.desc, :b.asc).qualify_to_first_source.sql.should == 'SELECT t.* FROM t ORDER BY t.a DESC, t.b ASC'
3401
+ end
3402
+
3403
+ specify "should handle SQL::AliasedExpressions" do
3404
+ @ds.select(:a.as(:b)).qualify_to_first_source.sql.should == 'SELECT t.a AS b FROM t'
3405
+ end
3406
+
3407
+ specify "should handle SQL::CaseExpressions" do
3408
+ @ds.filter{{a=>b}.case(c, d)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (CASE t.d WHEN t.a THEN t.b ELSE t.c END)'
3409
+ end
3410
+
3411
+ specify "should handle SQL:Casts" do
3412
+ @ds.filter{a.cast(:boolean)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE CAST(t.a AS boolean)'
3413
+ end
3414
+
3415
+ specify "should handle SQL::Functions" do
3416
+ @ds.filter{a(b, 1)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE a(t.b, 1)'
3417
+ end
3418
+
3419
+ specify "should handle SQL::ComplexExpressions" do
3420
+ @ds.filter{(a+b)<(c-3)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE ((t.a + t.b) < (t.c - 3))'
3421
+ end
3422
+
3423
+ specify "should handle SQL::SQLArrays" do
3424
+ @ds.filter(:a=>[:b, :c].sql_array).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.a IN (t.b, t.c))'
3425
+ end
3426
+
3427
+ specify "should handle SQL::Subscripts" do
3428
+ @ds.filter{a.sql_subscript(b,3)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE t.a[t.b, 3]'
3429
+ end
3430
+
3431
+ specify "should handle SQL::PlaceholderLiteralStrings" do
3432
+ @ds.filter('? > ?', :a, 1).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.a > 1)'
3433
+ end
3434
+
3435
+ specify "should handle SQL::PlaceholderLiteralStrings with named placeholders" do
3436
+ @ds.filter(':a > :b', :a=>:c, :b=>1).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.c > 1)'
3437
+ end
3438
+
3439
+ specify "should handle SQL::WindowFunctions" do
3440
+ @ds.meta_def(:supports_window_functions?){true}
3441
+ @ds.select{sum(:over, :args=>:a, :partition=>:b, :order=>:c){}}.qualify_to_first_source.sql.should == 'SELECT sum(t.a) OVER (PARTITION BY t.b ORDER BY t.c) FROM t'
3442
+ end
3443
+
3444
+ specify "should handle all other objects by returning them unchanged" do
3445
+ @ds.select("a").filter{a(3)}.filter('blah').order('true'.lit).group('a > ?'.lit(1)).having(false).qualify_to_first_source.sql.should == \
3446
+ "SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a > 1 HAVING 'f' ORDER BY true"
3447
+ end
3448
+ end
3449
+
3450
+ context "Sequel::Dataset #with and #with_recursive" do
3451
+ before do
3452
+ @db = MockDatabase.new
3453
+ @ds = @db[:t]
3454
+ end
3455
+
3456
+ specify "#with should take a name and dataset and use a WITH clause" do
3457
+ @ds.with(:t, @db[:x]).sql.should == 'WITH t AS (SELECT * FROM x) SELECT * FROM t'
3458
+ end
3459
+
3460
+ specify "#with_recursive should take a name, nonrecursive dataset, and recursive dataset, and use a WITH clause" do
3461
+ @ds.with_recursive(:t, @db[:x], @db[:t]).sql.should == 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
3462
+ end
3463
+
3464
+ specify "#with and #with_recursive should add to existing WITH clause if called multiple times" do
3465
+ @ds.with(:t, @db[:x]).with(:j, @db[:y]).sql.should == 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y) SELECT * FROM t'
3466
+ @ds.with_recursive(:t, @db[:x], @db[:t]).with_recursive(:j, @db[:y], @db[:j]).sql.should == 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t), j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t'
3467
+ @ds.with(:t, @db[:x]).with_recursive(:j, @db[:y], @db[:j]).sql.should == 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t'
3468
+ end
3469
+
3470
+ specify "#with and #with_recursive should take an :args option" do
3471
+ @ds.with(:t, @db[:x], :args=>[:b]).sql.should == 'WITH t(b) AS (SELECT * FROM x) SELECT * FROM t'
3472
+ @ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c]).sql.should == 'WITH t(b, c) AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
3473
+ end
3474
+
3475
+ specify "#with_recursive should take an :union_all=>false option" do
3476
+ @ds.with_recursive(:t, @db[:x], @db[:t], :union_all=>false).sql.should == 'WITH t AS (SELECT * FROM x UNION SELECT * FROM t) SELECT * FROM t'
3477
+ end
3478
+
3479
+ specify "#with and #with_recursive should raise an error unless the dataset supports CTEs" do
3480
+ @ds.meta_def(:supports_cte?){false}
3481
+ proc{@ds.with(:t, @db[:x], :args=>[:b])}.should raise_error(Sequel::Error)
3482
+ proc{@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c])}.should raise_error(Sequel::Error)
3483
+ end
3484
+ end
3485
+
3486
+ describe Sequel::SQL::Constants do
3487
+ before do
3488
+ @db = MockDatabase.new
3489
+ end
3490
+
3491
+ it "should have CURRENT_DATE" do
3492
+ @db.literal(Sequel::SQL::Constants::CURRENT_DATE) == 'CURRENT_DATE'
3493
+ @db.literal(Sequel::CURRENT_DATE) == 'CURRENT_DATE'
3494
+ end
3495
+
3496
+ it "should have CURRENT_TIME" do
3497
+ @db.literal(Sequel::SQL::Constants::CURRENT_TIME) == 'CURRENT_TIME'
3498
+ @db.literal(Sequel::CURRENT_TIME) == 'CURRENT_TIME'
3499
+ end
3500
+
3501
+ it "should have CURRENT_TIMESTAMP" do
3502
+ @db.literal(Sequel::SQL::Constants::CURRENT_TIMESTAMP) == 'CURRENT_TIMESTAMP'
3503
+ @db.literal(Sequel::CURRENT_TIMESTAMP) == 'CURRENT_TIMESTAMP'
3504
+ end
3505
+
3506
+ it "should have NULL" do
3507
+ @db.literal(Sequel::SQL::Constants::NULL) == 'NULL'
3508
+ @db.literal(Sequel::NULL) == 'NULL'
3509
+ end
3510
+
3511
+ it "should have NOTNULL" do
3512
+ @db.literal(Sequel::SQL::Constants::NOTNULL) == 'NOT NULL'
3513
+ @db.literal(Sequel::NOTNULL) == 'NOT NULL'
3514
+ end
3515
+
3516
+ it "should have TRUE and SQLTRUE" do
3517
+ @db.literal(Sequel::SQL::Constants::TRUE) == '1'
3518
+ @db.literal(Sequel::TRUE) == '1'
3519
+ @db.literal(Sequel::SQL::Constants::SQLTRUE) == '1'
3520
+ @db.literal(Sequel::SQLTRUE) == '1'
3521
+ end
3522
+
3523
+ it "should have FALSE and SQLFALSE" do
3524
+ @db.literal(Sequel::SQL::Constants::FALSE) == '0'
3525
+ @db.literal(Sequel::FALSE) == '0'
3526
+ @db.literal(Sequel::SQL::Constants::SQLFALSE) == '0'
3527
+ @db.literal(Sequel::SQLFALSE) == '0'
3528
+ end
3529
+ end
3530
+
3531
+ describe "Sequel timezone support" do
3532
+ before do
3533
+ @db = MockDatabase.new
3534
+ @dataset = @db.dataset
3535
+ @dataset.meta_def(:supports_timestamp_timezones?){true}
3536
+ @dataset.meta_def(:supports_timestamp_usecs?){false}
3537
+ @offset = sprintf("%+03i%02i", *(Time.now.utc_offset/60).divmod(60))
3538
+ end
3539
+ after do
3540
+ Sequel.default_timezone = nil
3541
+ Sequel.datetime_class = Time
3542
+ end
3543
+
3544
+ specify "should handle an database timezone of :utc when literalizing values" do
3545
+ Sequel.database_timezone = :utc
3546
+
3547
+ t = Time.now
3548
+ s = t.getutc.strftime("'%Y-%m-%d %H:%M:%S")
3549
+ @dataset.literal(t).should == "#{s}+0000'"
3550
+
3551
+ t = DateTime.now
3552
+ s = t.new_offset(0).strftime("'%Y-%m-%d %H:%M:%S")
3553
+ @dataset.literal(t).should == "#{s}+0000'"
3554
+ end
3555
+
3556
+ specify "should handle an database timezone of :local when literalizing values" do
3557
+ Sequel.database_timezone = :local
3558
+
3559
+ t = Time.now.utc
3560
+ s = t.getlocal.strftime("'%Y-%m-%d %H:%M:%S")
3561
+ @dataset.literal(t).should == "#{s}#{@offset}'"
3562
+
3563
+ t = DateTime.now.new_offset(0)
3564
+ s = t.new_offset(Sequel::LOCAL_DATETIME_OFFSET).strftime("'%Y-%m-%d %H:%M:%S")
3565
+ @dataset.literal(t).should == "#{s}#{@offset}'"
3566
+ end
3567
+
3568
+ specify "should handle converting database timestamps into application timestamps" do
3569
+ Sequel.database_timezone = :utc
3570
+ Sequel.application_timezone = :local
3571
+ t = Time.now.utc
3572
+ Sequel.database_to_application_timestamp(t).to_s.should == t.getlocal.to_s
3573
+ Sequel.database_to_application_timestamp(t.to_s).to_s.should == t.getlocal.to_s
3574
+ Sequel.database_to_application_timestamp(t.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == t.getlocal.to_s
3575
+
3576
+ Sequel.datetime_class = DateTime
3577
+ dt = DateTime.now
3578
+ dt2 = dt.new_offset(0)
3579
+ Sequel.database_to_application_timestamp(dt2).to_s.should == dt.to_s
3580
+ Sequel.database_to_application_timestamp(dt2.to_s).to_s.should == dt.to_s
3581
+ Sequel.database_to_application_timestamp(dt2.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == dt.to_s
3582
+
3583
+ Sequel.datetime_class = Time
3584
+ Sequel.database_timezone = :local
3585
+ Sequel.application_timezone = :utc
3586
+ Sequel.database_to_application_timestamp(t.getlocal).to_s.should == t.to_s
3587
+ Sequel.database_to_application_timestamp(t.getlocal.to_s).to_s.should == t.to_s
3588
+ Sequel.database_to_application_timestamp(t.getlocal.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == t.to_s
3589
+
3590
+ Sequel.datetime_class = DateTime
3591
+ Sequel.database_to_application_timestamp(dt).to_s.should == dt2.to_s
3592
+ Sequel.database_to_application_timestamp(dt.to_s).to_s.should == dt2.to_s
3593
+ Sequel.database_to_application_timestamp(dt.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == dt2.to_s
3594
+ end
3595
+
3596
+ specify "should handle typecasting timestamp columns" do
3597
+ Sequel.typecast_timezone = :utc
3598
+ Sequel.application_timezone = :local
3599
+ t = Time.now.utc
3600
+ @db.typecast_value(:datetime, t).to_s.should == t.getlocal.to_s
3601
+ @db.typecast_value(:datetime, t.to_s).to_s.should == t.getlocal.to_s
3602
+ @db.typecast_value(:datetime, t.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == t.getlocal.to_s
3603
+
3604
+ Sequel.datetime_class = DateTime
3605
+ dt = DateTime.now
3606
+ dt2 = dt.new_offset(0)
3607
+ @db.typecast_value(:datetime, dt2).to_s.should == dt.to_s
3608
+ @db.typecast_value(:datetime, dt2.to_s).to_s.should == dt.to_s
3609
+ @db.typecast_value(:datetime, dt2.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == dt.to_s
3610
+
3611
+ Sequel.datetime_class = Time
3612
+ Sequel.typecast_timezone = :local
3613
+ Sequel.application_timezone = :utc
3614
+ @db.typecast_value(:datetime, t.getlocal).to_s.should == t.to_s
3615
+ @db.typecast_value(:datetime, t.getlocal.to_s).to_s.should == t.to_s
3616
+ @db.typecast_value(:datetime, t.getlocal.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == t.to_s
3617
+
3618
+ Sequel.datetime_class = DateTime
3619
+ @db.typecast_value(:datetime, dt).to_s.should == dt2.to_s
3620
+ @db.typecast_value(:datetime, dt.to_s).to_s.should == dt2.to_s
3621
+ @db.typecast_value(:datetime, dt.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == dt2.to_s
3622
+ end
3623
+
3624
+ specify "should handle converting database timestamp columns from an array of values" do
3625
+ Sequel.database_timezone = :utc
3626
+ Sequel.application_timezone = :local
3627
+ t = Time.now.utc
3628
+ Sequel.database_to_application_timestamp([t.year, t.mon, t.day, t.hour, t.min, t.sec]).to_s.should == t.getlocal.to_s
3629
+
3630
+ Sequel.datetime_class = DateTime
3631
+ dt = DateTime.now
3632
+ dt2 = dt.new_offset(0)
3633
+ Sequel.database_to_application_timestamp([dt2.year, dt2.mon, dt2.day, dt2.hour, dt2.min, dt2.sec]).to_s.should == dt.to_s
3634
+
3635
+ Sequel.datetime_class = Time
3636
+ Sequel.database_timezone = :local
3637
+ Sequel.application_timezone = :utc
3638
+ t = t.getlocal
3639
+ Sequel.database_to_application_timestamp([t.year, t.mon, t.day, t.hour, t.min, t.sec]).to_s.should == t.getutc.to_s
3640
+
3641
+ Sequel.datetime_class = DateTime
3642
+ Sequel.database_to_application_timestamp([dt.year, dt.mon, dt.day, dt.hour, dt.min, dt.sec]).to_s.should == dt2.to_s
3643
+ end
3644
+
3645
+ specify "should raise an InvalidValue error when an error occurs while converting a timestamp" do
3646
+ proc{Sequel.database_to_application_timestamp([0, 0, 0, 0, 0, 0])}.should raise_error(Sequel::InvalidValue)
3647
+ end
3648
+
3649
+ specify "should raise an error when attempting to typecast to a timestamp from an unsupported type" do
3650
+ proc{Sequel.database_to_application_timestamp(Object.new)}.should raise_error(Sequel::InvalidValue)
3651
+ end
3652
+
3653
+ specify "should raise an InvalidValue error when the DateTime class is used and when a bad application timezone is used when attempting to convert timestamps" do
3654
+ Sequel.application_timezone = :blah
3655
+ Sequel.datetime_class = DateTime
3656
+ proc{Sequel.database_to_application_timestamp('2009-06-01 10:20:30')}.should raise_error(Sequel::InvalidValue)
3657
+ end
3658
+
3659
+ specify "should raise an InvalidValue error when the DateTime class is used and when a bad database timezone is used when attempting to convert timestamps" do
3660
+ Sequel.database_timezone = :blah
3661
+ Sequel.datetime_class = DateTime
3662
+ proc{Sequel.database_to_application_timestamp('2009-06-01 10:20:30')}.should raise_error(Sequel::InvalidValue)
3663
+ end
3664
+
3665
+ specify "should have Sequel.default_timezone= should set all other timezones" do
3666
+ Sequel.database_timezone.should == nil
3667
+ Sequel.application_timezone.should == nil
3668
+ Sequel.typecast_timezone.should == nil
3669
+ Sequel.default_timezone = :utc
3670
+ Sequel.database_timezone.should == :utc
3671
+ Sequel.application_timezone.should == :utc
3672
+ Sequel.typecast_timezone.should == :utc
3673
+ end
3674
+ end
3675
+
3676
+ context "Sequel::Dataset#select_map" do
3677
+ before do
3678
+ @ds = MockDatabase.new[:t]
3679
+ def @ds.fetch_rows(sql)
3680
+ db << sql
3681
+ yield({:c=>1})
3682
+ yield({:c=>2})
3683
+ end
3684
+ @ds.db.reset
3685
+ end
3686
+
3687
+ specify "should do select and map in one step" do
3688
+ @ds.select_map(:a).should == [1, 2]
3689
+ @ds.db.sqls.should == ['SELECT a FROM t']
3690
+ end
3691
+
3692
+ specify "should handle implicit qualifiers in arguments" do
3693
+ @ds.select_map(:a__b).should == [1, 2]
3694
+ @ds.db.sqls.should == ['SELECT a.b FROM t']
3695
+ end
3696
+
3697
+ specify "should handle implicit aliases in arguments" do
3698
+ @ds.select_map(:a___b).should == [1, 2]
3699
+ @ds.db.sqls.should == ['SELECT a AS b FROM t']
3700
+ end
3701
+
3702
+ specify "should handle other objects" do
3703
+ @ds.select_map("a".lit.as(:b)).should == [1, 2]
3704
+ @ds.db.sqls.should == ['SELECT a AS b FROM t']
3705
+ end
3706
+
3707
+ specify "should accept a block" do
3708
+ @ds.select_map{a(t__c)}.should == [1, 2]
3709
+ @ds.db.sqls.should == ['SELECT a(t.c) FROM t']
3710
+ end
3711
+ end
3712
+
3713
+ context "Sequel::Dataset#select_order_map" do
3714
+ before do
3715
+ @ds = MockDatabase.new[:t]
3716
+ def @ds.fetch_rows(sql)
3717
+ db << sql
3718
+ yield({:c=>1})
3719
+ yield({:c=>2})
3720
+ end
3721
+ @ds.db.reset
3722
+ end
3723
+
3724
+ specify "should do select and map in one step" do
3725
+ @ds.select_order_map(:a).should == [1, 2]
3726
+ @ds.db.sqls.should == ['SELECT a FROM t ORDER BY a']
3727
+ end
3728
+
3729
+ specify "should handle implicit qualifiers in arguments" do
3730
+ @ds.select_order_map(:a__b).should == [1, 2]
3731
+ @ds.db.sqls.should == ['SELECT a.b FROM t ORDER BY a.b']
3732
+ end
3733
+
3734
+ specify "should handle implicit aliases in arguments" do
3735
+ @ds.select_order_map(:a___b).should == [1, 2]
3736
+ @ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
3737
+ end
3738
+
3739
+ specify "should handle implicit qualifiers and aliases in arguments" do
3740
+ @ds.select_order_map(:t__a___b).should == [1, 2]
3741
+ @ds.db.sqls.should == ['SELECT t.a AS b FROM t ORDER BY t.a']
3742
+ end
3743
+
3744
+ specify "should handle AliasedExpressions" do
3745
+ @ds.select_order_map("a".lit.as(:b)).should == [1, 2]
3746
+ @ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
3747
+ end
3748
+
3749
+ specify "should accept a block" do
3750
+ @ds.select_order_map{a(t__c)}.should == [1, 2]
3751
+ @ds.db.sqls.should == ['SELECT a(t.c) FROM t ORDER BY a(t.c)']
3752
+ end
3753
+ end
3754
+
3755
+ context "Sequel::Dataset#select_hash" do
3756
+ before do
3757
+ @ds = MockDatabase.new[:t]
3758
+ def @ds.set_fr_yield(hs)
3759
+ @hs = hs
3760
+ end
3761
+ def @ds.fetch_rows(sql)
3762
+ db << sql
3763
+ @hs.each{|h| yield h}
3764
+ end
3765
+ @ds.db.reset
3766
+ end
3767
+
3768
+ specify "should do select and map in one step" do
3769
+ @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3770
+ @ds.select_hash(:a, :b).should == {1=>2, 3=>4}
3771
+ @ds.db.sqls.should == ['SELECT a, b FROM t']
3772
+ end
3773
+
3774
+ specify "should handle implicit qualifiers in arguments" do
3775
+ @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3776
+ @ds.select_hash(:t__a, :t__b).should == {1=>2, 3=>4}
3777
+ @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
3778
+ end
3779
+
3780
+ specify "should handle implicit aliases in arguments" do
3781
+ @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3782
+ @ds.select_hash(:c___a, :d___b).should == {1=>2, 3=>4}
3783
+ @ds.db.sqls.should == ['SELECT c AS a, d AS b FROM t']
3784
+ end
3785
+
3786
+ specify "should handle implicit qualifiers and aliases in arguments" do
3787
+ @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3788
+ @ds.select_hash(:t__c___a, :t__d___b).should == {1=>2, 3=>4}
3789
+ @ds.db.sqls.should == ['SELECT t.c AS a, t.d AS b FROM t']
3790
+ end
3791
+ end
3792
+
3793
+ context "Modifying joined datasets" do
3794
+ before do
3795
+ @ds = MockDatabase.new.from(:b, :c).join(:d, [:id]).where(:id => 2)
3796
+ @ds.meta_def(:supports_modifying_joins?){true}
3797
+ @ds.db.reset
3798
+ end
3799
+
3800
+ specify "should allow deleting from joined datasets" do
3801
+ @ds.delete
3802
+ @ds.db.sqls.should == ['DELETE FROM b, c WHERE (id = 2)']
3803
+ end
3804
+
3805
+ specify "should allow updating joined datasets" do
3806
+ @ds.update(:a=>1)
3807
+ @ds.db.sqls.should == ['UPDATE b, c INNER JOIN d USING (id) SET a = 1 WHERE (id = 2)']
3808
+ end
3809
+ end
3810
+
3811
+ context "Dataset#lock_style and for_update" do
3812
+ before do
3813
+ @ds = MockDatabase.new[:t]
3814
+ end
3815
+
3816
+ specify "#for_update should use FOR UPDATE" do
3817
+ @ds.for_update.sql.should == "SELECT * FROM t FOR UPDATE"
3818
+ end
3819
+
3820
+ specify "#lock_style should accept symbols" do
3821
+ @ds.lock_style(:update).sql.should == "SELECT * FROM t FOR UPDATE"
3822
+ end
3823
+
3824
+ specify "#lock_style should accept strings for arbitrary SQL" do
3825
+ @ds.lock_style("FOR SHARE").sql.should == "SELECT * FROM t FOR SHARE"
3826
+ end
3827
+ end