viking-sequel 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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