sequel 3.21.0 → 3.36.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 (311) hide show
  1. data/CHANGELOG +855 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +23 -7
  4. data/Rakefile +42 -24
  5. data/bin/sequel +26 -8
  6. data/doc/active_record.rdoc +1 -0
  7. data/doc/advanced_associations.rdoc +171 -11
  8. data/doc/association_basics.rdoc +293 -43
  9. data/doc/dataset_basics.rdoc +5 -5
  10. data/doc/dataset_filtering.rdoc +8 -0
  11. data/doc/mass_assignment.rdoc +56 -0
  12. data/doc/migration.rdoc +85 -552
  13. data/doc/model_hooks.rdoc +78 -27
  14. data/doc/object_model.rdoc +541 -0
  15. data/doc/opening_databases.rdoc +97 -50
  16. data/doc/prepared_statements.rdoc +40 -14
  17. data/doc/querying.rdoc +28 -4
  18. data/doc/reflection.rdoc +30 -4
  19. data/doc/release_notes/3.22.0.txt +39 -0
  20. data/doc/release_notes/3.23.0.txt +172 -0
  21. data/doc/release_notes/3.24.0.txt +420 -0
  22. data/doc/release_notes/3.25.0.txt +88 -0
  23. data/doc/release_notes/3.26.0.txt +88 -0
  24. data/doc/release_notes/3.27.0.txt +82 -0
  25. data/doc/release_notes/3.28.0.txt +304 -0
  26. data/doc/release_notes/3.29.0.txt +459 -0
  27. data/doc/release_notes/3.30.0.txt +135 -0
  28. data/doc/release_notes/3.31.0.txt +146 -0
  29. data/doc/release_notes/3.32.0.txt +202 -0
  30. data/doc/release_notes/3.33.0.txt +157 -0
  31. data/doc/release_notes/3.34.0.txt +671 -0
  32. data/doc/release_notes/3.35.0.txt +144 -0
  33. data/doc/release_notes/3.36.0.txt +245 -0
  34. data/doc/schema_modification.rdoc +572 -0
  35. data/doc/sharding.rdoc +95 -3
  36. data/doc/testing.rdoc +129 -0
  37. data/doc/thread_safety.rdoc +17 -0
  38. data/doc/transactions.rdoc +137 -0
  39. data/doc/virtual_rows.rdoc +51 -2
  40. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  41. data/lib/sequel/adapters/ado.rb +40 -18
  42. data/lib/sequel/adapters/amalgalite.rb +17 -9
  43. data/lib/sequel/adapters/db2.rb +175 -86
  44. data/lib/sequel/adapters/dbi.rb +15 -15
  45. data/lib/sequel/adapters/do/mysql.rb +17 -11
  46. data/lib/sequel/adapters/do/postgres.rb +1 -57
  47. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  48. data/lib/sequel/adapters/do.rb +18 -36
  49. data/lib/sequel/adapters/firebird.rb +27 -208
  50. data/lib/sequel/adapters/ibmdb.rb +453 -0
  51. data/lib/sequel/adapters/informix.rb +6 -23
  52. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  53. data/lib/sequel/adapters/jdbc/db2.rb +47 -0
  54. data/lib/sequel/adapters/jdbc/derby.rb +287 -0
  55. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  56. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  57. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  58. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  59. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  61. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  62. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
  64. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  65. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  66. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  67. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  68. data/lib/sequel/adapters/jdbc.rb +265 -94
  69. data/lib/sequel/adapters/mock.rb +361 -0
  70. data/lib/sequel/adapters/mysql.rb +118 -222
  71. data/lib/sequel/adapters/mysql2.rb +58 -57
  72. data/lib/sequel/adapters/odbc/mssql.rb +15 -7
  73. data/lib/sequel/adapters/odbc.rb +28 -9
  74. data/lib/sequel/adapters/openbase.rb +3 -5
  75. data/lib/sequel/adapters/oracle.rb +349 -53
  76. data/lib/sequel/adapters/postgres.rb +356 -107
  77. data/lib/sequel/adapters/shared/access.rb +32 -9
  78. data/lib/sequel/adapters/shared/db2.rb +342 -0
  79. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  80. data/lib/sequel/adapters/shared/informix.rb +53 -0
  81. data/lib/sequel/adapters/shared/mssql.rb +335 -118
  82. data/lib/sequel/adapters/shared/mysql.rb +340 -76
  83. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  84. data/lib/sequel/adapters/shared/oracle.rb +243 -45
  85. data/lib/sequel/adapters/shared/postgres.rb +524 -279
  86. data/lib/sequel/adapters/shared/progress.rb +5 -8
  87. data/lib/sequel/adapters/shared/sqlite.rb +265 -89
  88. data/lib/sequel/adapters/sqlite.rb +101 -39
  89. data/lib/sequel/adapters/swift/mysql.rb +9 -12
  90. data/lib/sequel/adapters/swift/postgres.rb +9 -74
  91. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  92. data/lib/sequel/adapters/swift.rb +8 -7
  93. data/lib/sequel/adapters/tinytds.rb +161 -21
  94. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  95. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  96. data/lib/sequel/ast_transformer.rb +194 -0
  97. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  98. data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
  99. data/lib/sequel/connection_pool/single.rb +5 -0
  100. data/lib/sequel/connection_pool/threaded.rb +38 -14
  101. data/lib/sequel/connection_pool.rb +10 -2
  102. data/lib/sequel/core.rb +118 -11
  103. data/lib/sequel/database/connecting.rb +38 -28
  104. data/lib/sequel/database/dataset.rb +4 -4
  105. data/lib/sequel/database/dataset_defaults.rb +63 -2
  106. data/lib/sequel/database/logging.rb +7 -2
  107. data/lib/sequel/database/misc.rb +150 -46
  108. data/lib/sequel/database/query.rb +215 -65
  109. data/lib/sequel/database/schema_generator.rb +64 -20
  110. data/lib/sequel/database/schema_methods.rb +212 -54
  111. data/lib/sequel/dataset/actions.rb +322 -85
  112. data/lib/sequel/dataset/features.rb +96 -8
  113. data/lib/sequel/dataset/graph.rb +38 -11
  114. data/lib/sequel/dataset/misc.rb +50 -23
  115. data/lib/sequel/dataset/mutation.rb +12 -7
  116. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  117. data/lib/sequel/dataset/query.rb +237 -44
  118. data/lib/sequel/dataset/sql.rb +698 -278
  119. data/lib/sequel/dataset.rb +8 -0
  120. data/lib/sequel/exceptions.rb +4 -0
  121. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  122. data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
  123. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  124. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
  125. data/lib/sequel/extensions/migration.rb +97 -11
  126. data/lib/sequel/extensions/named_timezones.rb +5 -0
  127. data/lib/sequel/extensions/null_dataset.rb +90 -0
  128. data/lib/sequel/extensions/pg_array.rb +460 -0
  129. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  130. data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
  131. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  132. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  133. data/lib/sequel/extensions/pg_inet.rb +89 -0
  134. data/lib/sequel/extensions/pg_json.rb +178 -0
  135. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  136. data/lib/sequel/extensions/pretty_table.rb +5 -71
  137. data/lib/sequel/extensions/query.rb +1 -1
  138. data/lib/sequel/extensions/query_literals.rb +79 -0
  139. data/lib/sequel/extensions/schema_caching.rb +76 -0
  140. data/lib/sequel/extensions/schema_dumper.rb +251 -37
  141. data/lib/sequel/extensions/select_remove.rb +35 -0
  142. data/lib/sequel/extensions/server_block.rb +139 -0
  143. data/lib/sequel/extensions/sql_expr.rb +4 -110
  144. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  145. data/lib/sequel/extensions/to_dot.rb +95 -83
  146. data/lib/sequel/model/associations.rb +1187 -393
  147. data/lib/sequel/model/base.rb +578 -170
  148. data/lib/sequel/model/errors.rb +1 -1
  149. data/lib/sequel/model/exceptions.rb +5 -1
  150. data/lib/sequel/model/inflections.rb +1 -1
  151. data/lib/sequel/model.rb +30 -11
  152. data/lib/sequel/no_core_ext.rb +2 -0
  153. data/lib/sequel/plugins/active_model.rb +13 -1
  154. data/lib/sequel/plugins/association_pks.rb +22 -4
  155. data/lib/sequel/plugins/caching.rb +25 -18
  156. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  157. data/lib/sequel/plugins/composition.rb +44 -12
  158. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  159. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  160. data/lib/sequel/plugins/dirty.rb +214 -0
  161. data/lib/sequel/plugins/eager_each.rb +59 -0
  162. data/lib/sequel/plugins/force_encoding.rb +6 -6
  163. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  164. data/lib/sequel/plugins/identity_map.rb +123 -8
  165. data/lib/sequel/plugins/instance_filters.rb +10 -0
  166. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  167. data/lib/sequel/plugins/json_serializer.rb +58 -6
  168. data/lib/sequel/plugins/list.rb +12 -2
  169. data/lib/sequel/plugins/many_through_many.rb +79 -28
  170. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  171. data/lib/sequel/plugins/nested_attributes.rb +139 -63
  172. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  173. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  174. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  175. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  176. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  177. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  178. data/lib/sequel/plugins/schema.rb +7 -2
  179. data/lib/sequel/plugins/serialization.rb +98 -49
  180. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  181. data/lib/sequel/plugins/sharding.rb +21 -54
  182. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  183. data/lib/sequel/plugins/static_cache.rb +99 -0
  184. data/lib/sequel/plugins/subclasses.rb +10 -2
  185. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  186. data/lib/sequel/plugins/timestamps.rb +1 -1
  187. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  188. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  189. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  190. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  191. data/lib/sequel/sql.rb +526 -59
  192. data/lib/sequel/timezones.rb +57 -38
  193. data/lib/sequel/version.rb +1 -1
  194. data/spec/adapters/db2_spec.rb +146 -0
  195. data/spec/adapters/firebird_spec.rb +1 -1
  196. data/spec/adapters/mssql_spec.rb +142 -40
  197. data/spec/adapters/mysql_spec.rb +380 -234
  198. data/spec/adapters/oracle_spec.rb +67 -77
  199. data/spec/adapters/postgres_spec.rb +1050 -212
  200. data/spec/adapters/spec_helper.rb +8 -5
  201. data/spec/adapters/sqlite_spec.rb +139 -27
  202. data/spec/core/connection_pool_spec.rb +196 -93
  203. data/spec/core/database_spec.rb +879 -510
  204. data/spec/core/dataset_spec.rb +1415 -1086
  205. data/spec/core/expression_filters_spec.rb +554 -109
  206. data/spec/core/mock_adapter_spec.rb +449 -0
  207. data/spec/core/object_graph_spec.rb +86 -114
  208. data/spec/core/schema_generator_spec.rb +4 -4
  209. data/spec/core/schema_spec.rb +482 -38
  210. data/spec/core/spec_helper.rb +6 -48
  211. data/spec/extensions/active_model_spec.rb +13 -0
  212. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  213. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  214. data/spec/extensions/association_dependencies_spec.rb +15 -25
  215. data/spec/extensions/association_pks_spec.rb +66 -32
  216. data/spec/extensions/association_proxies_spec.rb +4 -4
  217. data/spec/extensions/caching_spec.rb +47 -51
  218. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  219. data/spec/extensions/columns_introspection_spec.rb +91 -0
  220. data/spec/extensions/composition_spec.rb +18 -13
  221. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
  222. data/spec/extensions/dataset_associations_spec.rb +199 -0
  223. data/spec/extensions/defaults_setter_spec.rb +64 -0
  224. data/spec/extensions/dirty_spec.rb +155 -0
  225. data/spec/extensions/eager_each_spec.rb +34 -0
  226. data/spec/extensions/force_encoding_spec.rb +4 -2
  227. data/spec/extensions/hook_class_methods_spec.rb +69 -92
  228. data/spec/extensions/identity_map_spec.rb +131 -22
  229. data/spec/extensions/inflector_spec.rb +0 -4
  230. data/spec/extensions/instance_filters_spec.rb +11 -21
  231. data/spec/extensions/instance_hooks_spec.rb +72 -0
  232. data/spec/extensions/json_serializer_spec.rb +49 -6
  233. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  234. data/spec/extensions/list_spec.rb +48 -39
  235. data/spec/extensions/many_through_many_spec.rb +283 -250
  236. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  237. data/spec/extensions/migration_spec.rb +140 -35
  238. data/spec/extensions/named_timezones_spec.rb +29 -11
  239. data/spec/extensions/nested_attributes_spec.rb +251 -89
  240. data/spec/extensions/null_dataset_spec.rb +85 -0
  241. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  242. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  243. data/spec/extensions/pg_array_spec.rb +196 -0
  244. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  245. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  246. data/spec/extensions/pg_hstore_spec.rb +195 -0
  247. data/spec/extensions/pg_inet_spec.rb +44 -0
  248. data/spec/extensions/pg_json_spec.rb +101 -0
  249. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  250. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  251. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  252. data/spec/extensions/prepared_statements_spec.rb +87 -0
  253. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  254. data/spec/extensions/pretty_table_spec.rb +7 -6
  255. data/spec/extensions/query_literals_spec.rb +168 -0
  256. data/spec/extensions/query_spec.rb +6 -0
  257. data/spec/extensions/rcte_tree_spec.rb +50 -43
  258. data/spec/extensions/schema_caching_spec.rb +41 -0
  259. data/spec/extensions/schema_dumper_spec.rb +429 -22
  260. data/spec/extensions/schema_spec.rb +25 -21
  261. data/spec/extensions/select_remove_spec.rb +38 -0
  262. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  263. data/spec/extensions/serialization_spec.rb +85 -43
  264. data/spec/extensions/server_block_spec.rb +90 -0
  265. data/spec/extensions/sharding_spec.rb +69 -143
  266. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  267. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  268. data/spec/extensions/spec_helper.rb +22 -61
  269. data/spec/extensions/sql_expr_spec.rb +29 -60
  270. data/spec/extensions/static_cache_spec.rb +145 -0
  271. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  272. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  273. data/spec/extensions/timestamps_spec.rb +6 -6
  274. data/spec/extensions/to_dot_spec.rb +4 -7
  275. data/spec/extensions/touch_spec.rb +13 -14
  276. data/spec/extensions/tree_spec.rb +11 -26
  277. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  278. data/spec/extensions/update_primary_key_spec.rb +30 -24
  279. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  280. data/spec/extensions/validation_helpers_spec.rb +16 -35
  281. data/spec/extensions/xml_serializer_spec.rb +29 -4
  282. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  283. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  284. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  285. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  286. data/spec/integration/associations_test.rb +659 -21
  287. data/spec/integration/database_test.rb +9 -3
  288. data/spec/integration/dataset_test.rb +605 -91
  289. data/spec/integration/eager_loader_test.rb +19 -21
  290. data/spec/integration/migrator_test.rb +2 -3
  291. data/spec/integration/model_test.rb +81 -2
  292. data/spec/integration/plugin_test.rb +454 -143
  293. data/spec/integration/prepared_statement_test.rb +263 -128
  294. data/spec/integration/schema_test.rb +309 -43
  295. data/spec/integration/spec_helper.rb +40 -30
  296. data/spec/integration/timezone_test.rb +38 -12
  297. data/spec/integration/transaction_test.rb +183 -8
  298. data/spec/integration/type_test.rb +18 -4
  299. data/spec/model/association_reflection_spec.rb +201 -7
  300. data/spec/model/associations_spec.rb +1067 -661
  301. data/spec/model/base_spec.rb +329 -95
  302. data/spec/model/dataset_methods_spec.rb +7 -27
  303. data/spec/model/eager_loading_spec.rb +677 -676
  304. data/spec/model/hooks_spec.rb +349 -67
  305. data/spec/model/model_spec.rb +240 -168
  306. data/spec/model/plugins_spec.rb +24 -13
  307. data/spec/model/record_spec.rb +490 -232
  308. data/spec/model/spec_helper.rb +14 -71
  309. data/spec/model/validations_spec.rb +11 -0
  310. data/spec/rcov.opts +2 -0
  311. metadata +178 -80
@@ -60,12 +60,6 @@ describe "Dataset" do
60
60
  db = Sequel::Database.new(:identifier_output_method=>:downcase)
61
61
  db[:a].identifier_output_method.should == :downcase
62
62
  end
63
- end
64
-
65
- describe "Dataset" do
66
- before do
67
- @dataset = Sequel::Dataset.new("db")
68
- end
69
63
 
70
64
  specify "should have quote_identifiers= method which changes literalization of identifiers" do
71
65
  @dataset.quote_identifiers = true
@@ -111,27 +105,25 @@ describe "Dataset#clone" do
111
105
 
112
106
  specify "should create an exact copy of the dataset" do
113
107
  @dataset.row_proc = Proc.new{|r| r}
114
- @clone = @dataset.clone
108
+ clone = @dataset.clone
115
109
 
116
- @clone.object_id.should_not === @dataset.object_id
117
- @clone.class.should == @dataset.class
118
- @clone.opts.should == @dataset.opts
119
- @clone.row_proc.should == @dataset.row_proc
110
+ clone.object_id.should_not === @dataset.object_id
111
+ clone.class.should == @dataset.class
112
+ clone.db.should == @dataset.db
113
+ clone.opts.should == @dataset.opts
114
+ clone.row_proc.should == @dataset.row_proc
120
115
  end
121
116
 
122
117
  specify "should deep-copy the dataset opts" do
123
- @clone = @dataset.clone
118
+ clone = @dataset.clone
124
119
 
125
- @clone.opts.should_not equal(@dataset.opts)
120
+ clone.opts.should_not equal(@dataset.opts)
126
121
  @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
122
+ clone.opts[:filter].should be_nil
123
+
124
+ clone = @dataset.clone(:from => [:other])
125
+ @dataset.opts[:from].should == [:items]
126
+ clone.opts[:from].should == [:other]
135
127
  end
136
128
 
137
129
  specify "should merge the specified options" do
@@ -144,12 +136,6 @@ describe "Dataset#clone" do
144
136
  clone.opts.should == {:from => [:other]}
145
137
  end
146
138
 
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
139
  specify "should return an object with the same modules included" do
154
140
  m = Module.new do
155
141
  def __xyz__; "xyz"; end
@@ -161,7 +147,7 @@ end
161
147
 
162
148
  describe "Dataset#==" do
163
149
  before do
164
- @db = MockDatabase.new
150
+ @db = Sequel.mock
165
151
  @h = {}
166
152
  end
167
153
 
@@ -170,7 +156,7 @@ describe "Dataset#==" do
170
156
  end
171
157
 
172
158
  specify "should be different for datasets with different dbs" do
173
- @db[:t].should_not == MockDatabase.new[:t]
159
+ @db[:t].should_not == Sequel.mock[:t]
174
160
  end
175
161
 
176
162
  specify "should be different for datasets with different opts" do
@@ -186,7 +172,7 @@ end
186
172
 
187
173
  describe "Dataset#hash" do
188
174
  before do
189
- @db = MockDatabase.new
175
+ @db = Sequel.mock
190
176
  @h = {}
191
177
  end
192
178
 
@@ -197,7 +183,7 @@ describe "Dataset#hash" do
197
183
  end
198
184
 
199
185
  specify "should be different for datasets with different dbs" do
200
- @db[:t].hash.should_not == MockDatabase.new[:t].hash
186
+ @db[:t].hash.should_not == Sequel.mock[:t].hash
201
187
  end
202
188
 
203
189
  specify "should be different for datasets with different opts" do
@@ -228,10 +214,21 @@ describe "A simple dataset" do
228
214
  @dataset.truncate_sql.should == 'TRUNCATE TABLE test'
229
215
  end
230
216
 
217
+ specify "should format a truncate statement with multiple tables if supported" do
218
+ @dataset.meta_def(:check_truncation_allowed!){}
219
+ @dataset.from(:test, :test2).truncate_sql.should == 'TRUNCATE TABLE test, test2'
220
+ end
221
+
231
222
  specify "should format an insert statement with default values" do
232
223
  @dataset.insert_sql.should == 'INSERT INTO test DEFAULT VALUES'
233
224
  end
234
225
 
226
+ specify "should use a single column with a default value when the dataset doesn't support using insert statement with default values" do
227
+ @dataset.meta_def(:insert_supports_empty_values?){false}
228
+ @dataset.meta_def(:columns){[:a, :b]}
229
+ @dataset.insert_sql.should == 'INSERT INTO test (b) VALUES (DEFAULT)'
230
+ end
231
+
235
232
  specify "should format an insert statement with hash" do
236
233
  @dataset.insert_sql(:name => 'wxyz', :price => 342).
237
234
  should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
@@ -245,8 +242,6 @@ describe "A simple dataset" do
245
242
  end
246
243
 
247
244
  specify "should format an insert statement with an object that respond_to? :values" do
248
- dbb = Sequel::Database.new
249
-
250
245
  v = Object.new
251
246
  def v.values; {:a => 1}; end
252
247
 
@@ -261,29 +256,24 @@ describe "A simple dataset" do
261
256
  end
262
257
 
263
258
  specify "should format an insert statement with sub-query" do
264
- @sub = Sequel::Dataset.new(nil).from(:something).filter(:x => 2)
265
- @dataset.insert_sql(@sub).should == \
266
- "INSERT INTO test SELECT * FROM something WHERE (x = 2)"
259
+ @dataset.insert_sql(@dataset.from(:something).filter(:x => 2)).should == "INSERT INTO test SELECT * FROM something WHERE (x = 2)"
267
260
  end
268
261
 
269
262
  specify "should format an insert statement with array" do
270
- @dataset.insert_sql('a', 2, 6.5).should ==
271
- "INSERT INTO test VALUES ('a', 2, 6.5)"
263
+ @dataset.insert_sql('a', 2, 6.5).should == "INSERT INTO test VALUES ('a', 2, 6.5)"
272
264
  end
273
265
 
274
266
  specify "should format an update statement" do
275
- @dataset.update_sql(:name => 'abc').should ==
276
- "UPDATE test SET name = 'abc'"
267
+ @dataset.update_sql(:name => 'abc').should == "UPDATE test SET name = 'abc'"
277
268
  end
278
269
 
279
270
  specify "should be able to return rows for arbitrary SQL" do
280
- @dataset.clone(:sql => 'xxx yyy zzz').select_sql.should ==
281
- "xxx yyy zzz"
271
+ @dataset.clone(:sql => 'xxx yyy zzz').select_sql.should == "xxx yyy zzz"
282
272
  end
283
273
 
284
274
  specify "should use the :sql option for all sql methods" do
285
275
  sql = "X"
286
- ds = Sequel::Dataset.new(nil, :sql=>sql)
276
+ ds = @dataset.clone(:sql=>sql)
287
277
  ds.sql.should == sql
288
278
  ds.select_sql.should == sql
289
279
  ds.insert_sql.should == sql
@@ -357,12 +347,12 @@ describe "Dataset#exists" do
357
347
  @ds1.filter(@ds2.exists).sql.should ==
358
348
  'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
359
349
  @ds1.filter(@ds2.exists & @ds3.exists).sql.should ==
360
- 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)) AND EXISTS (SELECT * FROM test WHERE (price > 50)))'
350
+ 'SELECT * FROM test WHERE ((EXISTS (SELECT * FROM test WHERE (price < 100))) AND (EXISTS (SELECT * FROM test WHERE (price > 50))))'
361
351
  end
362
352
 
363
353
  specify "should work in select" do
364
354
  @ds1.select(@ds2.exists.as(:a), @ds3.exists.as(:b)).sql.should ==
365
- 'SELECT EXISTS (SELECT * FROM test WHERE (price < 100)) AS a, EXISTS (SELECT * FROM test WHERE (price > 50)) AS b FROM test'
355
+ 'SELECT (EXISTS (SELECT * FROM test WHERE (price < 100))) AS a, (EXISTS (SELECT * FROM test WHERE (price > 50))) AS b FROM test'
366
356
  end
367
357
  end
368
358
 
@@ -418,7 +408,7 @@ describe "Dataset#where" do
418
408
  b.should == a
419
409
  end
420
410
 
421
- specify "should not replace named placeholders that don't existin in the hash" do
411
+ specify "should not replace named placeholders that don't exist in the hash" do
422
412
  @dataset.where('price < :price AND id in :ids', :price=>100).select_sql.should ==
423
413
  "SELECT * FROM test WHERE (price < 100 AND id in :ids)"
424
414
  end
@@ -510,62 +500,89 @@ describe "Dataset#where" do
510
500
  "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
511
501
  end
512
502
 
503
+ specify "should handle all types of IN/NOT IN queries with empty arrays" do
504
+ @dataset.filter(:id => []).sql.should == "SELECT * FROM test WHERE (id != id)"
505
+ @dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
506
+ @dataset.exclude(:id => []).sql.should == "SELECT * FROM test WHERE (id = id)"
507
+ @dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 = id1) AND (id2 = id2))"
508
+ end
509
+
510
+ specify "should handle all types of IN/NOT IN queries with empty arrays" do
511
+ begin
512
+ Sequel.empty_array_handle_nulls = false
513
+ @dataset.filter(:id => []).sql.should == "SELECT * FROM test WHERE (1 = 0)"
514
+ @dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 0)"
515
+ @dataset.exclude(:id => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
516
+ @dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
517
+ ensure
518
+ Sequel.empty_array_handle_nulls = true
519
+ end
520
+ end
521
+
513
522
  specify "should handle all types of IN/NOT IN queries" do
514
523
  @dataset.filter(:id => @d1.select(:id)).sql.should == "SELECT * FROM test WHERE (id IN (SELECT id FROM test WHERE (region = 'Asia')))"
515
- @dataset.filter(:id => []).sql.should == "SELECT * FROM test WHERE (id != id)"
516
524
  @dataset.filter(:id => [1, 2]).sql.should == "SELECT * FROM test WHERE (id IN (1, 2))"
517
525
  @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')))"
518
- @dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
519
526
  @dataset.filter([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE ((id1, id2) IN ((1, 2), (3, 4)))"
520
527
  @dataset.filter([:id1, :id2] => [[1, 2], [3,4]]).sql.should == "SELECT * FROM test WHERE ((id1, id2) IN ((1, 2), (3, 4)))"
521
528
 
522
529
  @dataset.exclude(:id => @d1.select(:id)).sql.should == "SELECT * FROM test WHERE (id NOT IN (SELECT id FROM test WHERE (region = 'Asia')))"
523
- @dataset.exclude(:id => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
524
530
  @dataset.exclude(:id => [1, 2]).sql.should == "SELECT * FROM test WHERE (id NOT IN (1, 2))"
525
531
  @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')))"
526
- @dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
527
532
  @dataset.exclude([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE ((id1, id2) NOT IN ((1, 2), (3, 4)))"
528
533
  @dataset.exclude([:id1, :id2] => [[1, 2], [3,4]]).sql.should == "SELECT * FROM test WHERE ((id1, id2) NOT IN ((1, 2), (3, 4)))"
529
534
  end
530
535
 
531
536
  specify "should handle IN/NOT IN queries with multiple columns and an array where the database doesn't support it" do
532
537
  @dataset.meta_def(:supports_multiple_column_in?){false}
533
- @dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
534
538
  @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)))"
535
- @dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
536
539
  @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)))"
537
540
  end
538
541
 
539
542
  specify "should handle IN/NOT IN queries with multiple columns and a dataset where the database doesn't support it" do
540
543
  @dataset.meta_def(:supports_multiple_column_in?){false}
541
- d1 = @d1.select(:id1, :id2)
542
- def d1.fetch_rows(sql)
543
- @sql_used = sql
544
- @columns = [:id1, :id2]
545
- yield(:id1=>1, :id2=>2)
546
- yield(:id1=>3, :id2=>4)
547
- end
548
- d1.instance_variable_get(:@sql_used).should == nil
544
+ db = Sequel.mock(:fetch=>[{:id1=>1, :id2=>2}, {:id1=>3, :id2=>4}])
545
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia').columns(:id1, :id2)
549
546
  @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
550
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
551
- d1.instance_variable_set(:@sql_used, nil)
547
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
552
548
  @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
553
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
549
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
554
550
  end
555
551
 
556
552
  specify "should handle IN/NOT IN queries with multiple columns and an empty dataset where the database doesn't support it" do
557
553
  @dataset.meta_def(:supports_multiple_column_in?){false}
558
- d1 = @d1.select(:id1, :id2)
559
- def d1.fetch_rows(sql)
560
- @sql_used = sql
561
- @columns = [:id1, :id2]
562
- end
563
- d1.instance_variable_get(:@sql_used).should == nil
554
+ db = Sequel.mock
555
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia').columns(:id1, :id2)
564
556
  @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
565
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
566
- d1.instance_variable_set(:@sql_used, nil)
567
- @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (1 = 1)"
568
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
557
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
558
+ @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE ((id1 = id1) AND (id2 = id2))"
559
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
560
+ end
561
+
562
+ specify "should handle IN/NOT IN queries with multiple columns and an empty dataset where the database doesn't support it with correct NULL handling" do
563
+ begin
564
+ Sequel.empty_array_handle_nulls = false
565
+ @dataset.meta_def(:supports_multiple_column_in?){false}
566
+ db = Sequel.mock
567
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia').columns(:id1, :id2)
568
+ @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (1 = 0)"
569
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
570
+ @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (1 = 1)"
571
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
572
+ ensure
573
+ Sequel.empty_array_handle_nulls = true
574
+ end
575
+ end
576
+
577
+ specify "should handle IN/NOT IN queries for datasets with row_procs" do
578
+ @dataset.meta_def(:supports_multiple_column_in?){false}
579
+ db = Sequel.mock(:fetch=>[{:id1=>1, :id2=>2}, {:id1=>3, :id2=>4}])
580
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia').columns(:id1, :id2)
581
+ d1.row_proc = proc{|h| Object.new}
582
+ @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
583
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
584
+ @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
585
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
569
586
  end
570
587
 
571
588
  specify "should accept a subquery for an EXISTS clause" do
@@ -601,10 +618,18 @@ describe "Dataset#where" do
601
618
  end
602
619
 
603
620
  specify "should accept true and false as arguments" do
604
- @dataset.filter(true).sql.should ==
605
- "SELECT * FROM test WHERE 't'"
606
- @dataset.filter(false).sql.should ==
607
- "SELECT * FROM test WHERE 'f'"
621
+ @dataset.filter(true).sql.should == "SELECT * FROM test WHERE 't'"
622
+ @dataset.filter(Sequel::SQLTRUE).sql.should == "SELECT * FROM test WHERE 't'"
623
+ @dataset.filter(false).sql.should == "SELECT * FROM test WHERE 'f'"
624
+ @dataset.filter(Sequel::SQLFALSE).sql.should == "SELECT * FROM test WHERE 'f'"
625
+ end
626
+
627
+ specify "should use boolean expression if dataset does not support where true/false" do
628
+ def @dataset.supports_where_true?() false end
629
+ @dataset.filter(true).sql.should == "SELECT * FROM test WHERE (1 = 1)"
630
+ @dataset.filter(Sequel::SQLTRUE).sql.should == "SELECT * FROM test WHERE (1 = 1)"
631
+ @dataset.filter(false).sql.should == "SELECT * FROM test WHERE (1 = 0)"
632
+ @dataset.filter(Sequel::SQLFALSE).sql.should == "SELECT * FROM test WHERE (1 = 0)"
608
633
  end
609
634
 
610
635
  specify "should allow the use of multiple arguments" do
@@ -658,33 +683,25 @@ describe "Dataset#or" do
658
683
  end
659
684
 
660
685
  specify "should add an alternative expression to the where clause" do
661
- @d1.or(:y => 2).sql.should ==
662
- 'SELECT * FROM test WHERE ((x = 1) OR (y = 2))'
686
+ @d1.or(:y => 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (y = 2))'
663
687
  end
664
688
 
665
689
  specify "should accept all forms of filters" do
666
- @d1.or('y > ?', 2).sql.should ==
667
- 'SELECT * FROM test WHERE ((x = 1) OR (y > 2))'
668
- @d1.or(:yy.sql_number > 3).sql.should ==
669
- 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
690
+ @d1.or('y > ?', 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (y > 2))'
691
+ @d1.or(:yy.sql_number > 3).sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
670
692
  end
671
693
 
672
694
  specify "should accept blocks passed to filter" do
673
- @d1.or{:yy.sql_number > 3}.sql.should ==
674
- 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
695
+ @d1.or{:yy.sql_number > 3}.sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
675
696
  end
676
697
 
677
698
  specify "should correctly add parens to give predictable results" do
678
- @d1.filter(:y => 2).or(:z => 3).sql.should ==
679
- 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
680
-
681
- @d1.or(:y => 2).filter(:z => 3).sql.should ==
682
- 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
699
+ @d1.filter(:y => 2).or(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
700
+ @d1.or(:y => 2).filter(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
683
701
  end
684
702
 
685
703
  specify "should allow the use of blocks and arguments simultaneously" do
686
- @d1.or(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should ==
687
- 'SELECT * FROM test WHERE ((x = 1) OR ((zz < 3) AND (yy > 3)))'
704
+ @d1.or(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should == 'SELECT * FROM test WHERE ((x = 1) OR ((zz < 3) AND (yy > 3)))'
688
705
  end
689
706
  end
690
707
 
@@ -697,34 +714,25 @@ describe "Dataset#and" do
697
714
  specify "should raise if no filter exists" do
698
715
  proc {@dataset.and(:a => 1)}.should raise_error(Sequel::Error)
699
716
  proc {@dataset.where(:a => 1).group(:t).and(:b => 2)}.should_not raise_error(Sequel::Error)
700
- @dataset.where(:a => 1).group(:t).and(:b => 2).sql ==
701
- "SELECT * FROM test WHERE (a = 1) AND (b = 2) GROUP BY t"
717
+ @dataset.where(:a => 1).group(:t).and(:b => 2).sql.should == "SELECT * FROM test WHERE ((a = 1) AND (b = 2)) GROUP BY t"
702
718
  end
703
719
 
704
720
  specify "should add an alternative expression to the where clause" do
705
- @d1.and(:y => 2).sql.should ==
706
- 'SELECT * FROM test WHERE ((x = 1) AND (y = 2))'
721
+ @d1.and(:y => 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (y = 2))'
707
722
  end
708
723
 
709
- specify "should accept all forms of filters" do
710
- # probably not exhaustive, but good enough
711
- @d1.and('y > ?', 2).sql.should ==
712
- 'SELECT * FROM test WHERE ((x = 1) AND (y > 2))'
713
- @d1.and(:yy.sql_number > 3).sql.should ==
714
- 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
724
+ specify "should accept different types of filters" do
725
+ @d1.and('y > ?', 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (y > 2))'
726
+ @d1.and(:yy.sql_number > 3).sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
715
727
  end
716
728
 
717
729
  specify "should accept blocks passed to filter" do
718
- @d1.and {:yy.sql_number > 3}.sql.should ==
719
- 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
730
+ @d1.and {:yy.sql_number > 3}.sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
720
731
  end
721
732
 
722
733
  specify "should correctly add parens to give predictable results" do
723
- @d1.or(:y => 2).and(:z => 3).sql.should ==
724
- 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
725
-
726
- @d1.and(:y => 2).or(:z => 3).sql.should ==
727
- 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
734
+ @d1.or(:y => 2).and(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
735
+ @d1.and(:y => 2).or(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
728
736
  end
729
737
  end
730
738
 
@@ -734,8 +742,11 @@ describe "Dataset#exclude" do
734
742
  end
735
743
 
736
744
  specify "should correctly negate the expression when one condition is given" do
737
- @dataset.exclude(:region=>'Asia').select_sql.should ==
738
- "SELECT * FROM test WHERE (region != 'Asia')"
745
+ @dataset.exclude(:region=>'Asia').select_sql.should == "SELECT * FROM test WHERE (region != 'Asia')"
746
+ end
747
+
748
+ specify "should affect the having clause if having clause is already used" do
749
+ @dataset.group_and_count(:name).having{count > 2}.exclude{count > 5}.sql.should == "SELECT name, count(*) AS count FROM test GROUP BY name HAVING ((count > 2) AND (count <= 5))"
739
750
  end
740
751
 
741
752
  specify "should take multiple conditions as a hash and express the logic correctly in SQL" do
@@ -745,30 +756,46 @@ describe "Dataset#exclude" do
745
756
  end
746
757
 
747
758
  specify "should parenthesize a single string condition correctly" do
748
- @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should ==
749
- "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
759
+ @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should == "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
750
760
  end
751
761
 
752
762
  specify "should parenthesize an array condition correctly" do
753
- @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should ==
754
- "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
763
+ @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should == "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
755
764
  end
756
765
 
757
766
  specify "should correctly parenthesize when it is used twice" do
758
- @dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should ==
759
- "SELECT * FROM test WHERE ((region != 'Asia') AND (name != 'Japan'))"
767
+ @dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should == "SELECT * FROM test WHERE ((region != 'Asia') AND (name != 'Japan'))"
760
768
  end
761
769
 
762
770
  specify "should support proc expressions" do
763
- @dataset.exclude{:id.sql_number < 6}.sql.should ==
764
- 'SELECT * FROM test WHERE (id >= 6)'
771
+ @dataset.exclude{:id.sql_number < 6}.sql.should == 'SELECT * FROM test WHERE (id >= 6)'
765
772
  end
766
773
 
767
774
  specify "should allow the use of blocks and arguments simultaneously" do
768
- @dataset.exclude(:id => (7..11)){:id.sql_number < 6}.sql.should ==
769
- 'SELECT * FROM test WHERE ((id < 7) OR (id > 11) OR (id >= 6))'
770
- @dataset.exclude([:id, 1], [:x, 3]){:id.sql_number < 6}.sql.should ==
771
- 'SELECT * FROM test WHERE ((id != 1) OR (x != 3) OR (id >= 6))'
775
+ @dataset.exclude(:id => (7..11)){:id.sql_number < 6}.sql.should == 'SELECT * FROM test WHERE ((id < 7) OR (id > 11) OR (id >= 6))'
776
+ @dataset.exclude([:id, 1], [:x, 3]){:id.sql_number < 6}.sql.should == 'SELECT * FROM test WHERE ((id != 1) OR (x != 3) OR (id >= 6))'
777
+ end
778
+ end
779
+
780
+ describe "Dataset#exclude_where" do
781
+ before do
782
+ @dataset = Sequel::Dataset.new(nil).from(:test)
783
+ end
784
+
785
+ specify "should correctly negate the expression and add it to the where clause" do
786
+ @dataset.exclude_where(:region=>'Asia').sql.should == "SELECT * FROM test WHERE (region != 'Asia')"
787
+ @dataset.exclude_where(:region=>'Asia').exclude_where(:region=>'NA').sql.should == "SELECT * FROM test WHERE ((region != 'Asia') AND (region != 'NA'))"
788
+ end
789
+
790
+ specify "should affect the where clause even if having clause is already used" do
791
+ @dataset.group_and_count(:name).having{count > 2}.exclude_where(:region=>'Asia').sql.should ==
792
+ "SELECT name, count(*) AS count FROM test WHERE (region != 'Asia') GROUP BY name HAVING (count > 2)"
793
+ end
794
+ end
795
+
796
+ describe "Dataset#exclude_having" do
797
+ specify "should correctly negate the expression and add it to the having clause" do
798
+ Sequel::Dataset.new(nil).from(:test).exclude_having{count > 2}.exclude_having{count < 0}.sql.should == "SELECT * FROM test HAVING ((count <= 2) AND (count >= 0))"
772
799
  end
773
800
  end
774
801
 
@@ -794,9 +821,6 @@ describe "Dataset#having" do
794
821
  before do
795
822
  @dataset = Sequel::Dataset.new(nil).from(:test)
796
823
  @grouped = @dataset.group(:region).select(:region, :sum.sql_function(:population), :avg.sql_function(:gdp))
797
- @d1 = @grouped.having('sum(population) > 10')
798
- @d2 = @grouped.having(:region => 'Asia')
799
- @columns = "region, sum(population), avg(gdp)"
800
824
  end
801
825
 
802
826
  specify "should just clone if given an empty argument" do
@@ -806,18 +830,15 @@ describe "Dataset#having" do
806
830
  end
807
831
 
808
832
  specify "should affect select statements" do
809
- @d1.select_sql.should ==
810
- "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
833
+ @grouped.having('sum(population) > 10').select_sql.should == "SELECT region, sum(population), avg(gdp) FROM test GROUP BY region HAVING (sum(population) > 10)"
811
834
  end
812
835
 
813
836
  specify "should support proc expressions" do
814
- @grouped.having {:sum.sql_function(:population) > 10}.sql.should ==
815
- "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
837
+ @grouped.having {:sum.sql_function(:population) > 10}.sql.should == "SELECT region, sum(population), avg(gdp) FROM test GROUP BY region HAVING (sum(population) > 10)"
816
838
  end
817
839
 
818
840
  specify "should work with and on the having clause" do
819
- @grouped.having( :a.sql_number > 1 ).and( :b.sql_number < 2 ).sql.should ==
820
- "SELECT #{@columns} FROM test GROUP BY region HAVING ((a > 1) AND (b < 2))"
841
+ @grouped.having( :a.sql_number > 1 ).and( :b.sql_number < 2 ).sql.should == "SELECT region, sum(population), avg(gdp) FROM test GROUP BY region HAVING ((a > 1) AND (b < 2))"
821
842
  end
822
843
  end
823
844
 
@@ -848,7 +869,7 @@ describe "a grouped dataset" do
848
869
  end
849
870
 
850
871
  specify "should format the right statement for counting (as a subquery)" do
851
- db = MockDatabase.new
872
+ db = Sequel.mock
852
873
  db[:test].select(:name).group(:name).count
853
874
  db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT name FROM test GROUP BY name) AS t1 LIMIT 1"]
854
875
  end
@@ -856,7 +877,7 @@ end
856
877
 
857
878
  describe "Dataset#group_by" do
858
879
  before do
859
- @dataset = Sequel::Dataset.new(nil).from(:test).group_by(:type_id)
880
+ @dataset = Sequel.mock[:test].group_by(:type_id)
860
881
  end
861
882
 
862
883
  specify "should raise when trying to generate an update statement" do
@@ -868,45 +889,102 @@ describe "Dataset#group_by" do
868
889
  end
869
890
 
870
891
  specify "should specify the grouping in generated select statement" do
871
- @dataset.select_sql.should ==
872
- "SELECT * FROM test GROUP BY type_id"
873
- @dataset.group_by(:a, :b).select_sql.should ==
874
- "SELECT * FROM test GROUP BY a, b"
875
- @dataset.group_by(:type_id=>nil).select_sql.should ==
876
- "SELECT * FROM test GROUP BY (type_id IS NULL)"
892
+ @dataset.select_sql.should == "SELECT * FROM test GROUP BY type_id"
893
+ @dataset.group_by(:a, :b).select_sql.should == "SELECT * FROM test GROUP BY a, b"
894
+ @dataset.group_by(:type_id=>nil).select_sql.should == "SELECT * FROM test GROUP BY (type_id IS NULL)"
877
895
  end
878
896
 
879
- specify "should ungroup when passed nil, empty, or no arguments" do
880
- @dataset.group_by.select_sql.should ==
881
- "SELECT * FROM test"
882
- @dataset.group_by(nil).select_sql.should ==
883
- "SELECT * FROM test"
897
+ specify "should ungroup when passed nil or no arguments" do
898
+ @dataset.group_by.select_sql.should == "SELECT * FROM test"
899
+ @dataset.group_by(nil).select_sql.should == "SELECT * FROM test"
884
900
  end
885
901
 
886
902
  specify "should undo previous grouping" do
887
- @dataset.group_by(:a).group_by(:b).select_sql.should ==
888
- "SELECT * FROM test GROUP BY b"
889
- @dataset.group_by(:a, :b).group_by.select_sql.should ==
890
- "SELECT * FROM test"
903
+ @dataset.group_by(:a).group_by(:b).select_sql.should == "SELECT * FROM test GROUP BY b"
904
+ @dataset.group_by(:a, :b).group_by.select_sql.should == "SELECT * FROM test"
891
905
  end
892
906
 
893
907
  specify "should be aliased as #group" do
894
- @dataset.group(:type_id=>nil).select_sql.should ==
895
- "SELECT * FROM test GROUP BY (type_id IS NULL)"
908
+ @dataset.group(:type_id=>nil).select_sql.should == "SELECT * FROM test GROUP BY (type_id IS NULL)"
909
+ end
910
+
911
+ specify "should take a virtual row block" do
912
+ @dataset.group{type_id > 1}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1)"
913
+ @dataset.group_by{type_id > 1}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1)"
914
+ @dataset.group{[type_id > 1, type_id < 2]}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1), (type_id < 2)"
915
+ @dataset.group(:foo){type_id > 1}.sql.should == "SELECT * FROM test GROUP BY foo, (type_id > 1)"
916
+ end
917
+
918
+ specify "should support a #group_rollup method if the database supports it" do
919
+ @dataset.meta_def(:supports_group_rollup?){true}
920
+ @dataset.group(:type_id).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY ROLLUP(type_id)"
921
+ @dataset.group(:type_id, :b).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY ROLLUP(type_id, b)"
922
+ @dataset.meta_def(:uses_with_rollup?){true}
923
+ @dataset.group(:type_id).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY type_id WITH ROLLUP"
924
+ @dataset.group(:type_id, :b).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY type_id, b WITH ROLLUP"
925
+ end
926
+
927
+ specify "should support a #group_cube method if the database supports it" do
928
+ @dataset.meta_def(:supports_group_cube?){true}
929
+ @dataset.group(:type_id).group_cube.select_sql.should == "SELECT * FROM test GROUP BY CUBE(type_id)"
930
+ @dataset.group(:type_id, :b).group_cube.select_sql.should == "SELECT * FROM test GROUP BY CUBE(type_id, b)"
931
+ @dataset.meta_def(:uses_with_rollup?){true}
932
+ @dataset.group(:type_id).group_cube.select_sql.should == "SELECT * FROM test GROUP BY type_id WITH CUBE"
933
+ @dataset.group(:type_id, :b).group_cube.select_sql.should == "SELECT * FROM test GROUP BY type_id, b WITH CUBE"
934
+ end
935
+
936
+ specify "should have #group_cube and #group_rollup methods raise an Error if not supported it" do
937
+ proc{@dataset.group(:type_id).group_rollup}.should raise_error(Sequel::Error)
938
+ proc{@dataset.group(:type_id).group_cube}.should raise_error(Sequel::Error)
896
939
  end
897
940
  end
898
941
 
899
942
  describe "Dataset#as" do
900
943
  specify "should set up an alias" do
901
944
  dataset = Sequel::Dataset.new(nil).from(:test)
902
- dataset.select(dataset.limit(1).select(:name).as(:n)).sql.should == \
903
- 'SELECT (SELECT name FROM test LIMIT 1) AS n FROM test'
945
+ dataset.select(dataset.limit(1).select(:name).as(:n)).sql.should == 'SELECT (SELECT name FROM test LIMIT 1) AS n FROM test'
904
946
  end
905
947
  end
906
948
 
907
949
  describe "Dataset#literal" do
908
950
  before do
909
- @dataset = Sequel::Dataset.new(nil).from(:test)
951
+ @ds = Sequel::Database.new.dataset
952
+ end
953
+
954
+ specify "should convert qualified symbol notation into dot notation" do
955
+ @ds.literal(:abc__def).should == 'abc.def'
956
+ end
957
+
958
+ specify "should convert AS symbol notation into SQL AS notation" do
959
+ @ds.literal(:xyz___x).should == 'xyz AS x'
960
+ @ds.literal(:abc__def___x).should == 'abc.def AS x'
961
+ end
962
+
963
+ specify "should support names with digits" do
964
+ @ds.literal(:abc2).should == 'abc2'
965
+ @ds.literal(:xx__yy3).should == 'xx.yy3'
966
+ @ds.literal(:ab34__temp3_4ax).should == 'ab34.temp3_4ax'
967
+ @ds.literal(:x1___y2).should == 'x1 AS y2'
968
+ @ds.literal(:abc2__def3___ggg4).should == 'abc2.def3 AS ggg4'
969
+ end
970
+
971
+ specify "should support upper case and lower case" do
972
+ @ds.literal(:ABC).should == 'ABC'
973
+ @ds.literal(:Zvashtoy__aBcD).should == 'Zvashtoy.aBcD'
974
+ end
975
+
976
+ specify "should support spaces inside column names" do
977
+ @ds.quote_identifiers = true
978
+ @ds.literal(:"AB C").should == '"AB C"'
979
+ @ds.literal(:"Zvas htoy__aB cD").should == '"Zvas htoy"."aB cD"'
980
+ @ds.literal(:"aB cD___XX XX").should == '"aB cD" AS "XX XX"'
981
+ @ds.literal(:"Zva shtoy__aB cD___XX XX").should == '"Zva shtoy"."aB cD" AS "XX XX"'
982
+ end
983
+ end
984
+
985
+ describe "Dataset#literal" do
986
+ before do
987
+ @dataset = Sequel::Database.new.from(:test)
910
988
  end
911
989
 
912
990
  specify "should escape strings properly" do
@@ -914,9 +992,9 @@ describe "Dataset#literal" do
914
992
  @dataset.literal('a"x"bc').should == "'a\"x\"bc'"
915
993
  @dataset.literal("a'bc").should == "'a''bc'"
916
994
  @dataset.literal("a''bc").should == "'a''''bc'"
917
- @dataset.literal("a\\bc").should == "'a\\\\bc'"
918
- @dataset.literal("a\\\\bc").should == "'a\\\\\\\\bc'"
919
- @dataset.literal("a\\'bc").should == "'a\\\\''bc'"
995
+ @dataset.literal("a\\bc").should == "'a\\bc'"
996
+ @dataset.literal("a\\\\bc").should == "'a\\\\bc'"
997
+ @dataset.literal("a\\'bc").should == "'a\\''bc'"
920
998
  end
921
999
 
922
1000
  specify "should escape blobs as strings by default" do
@@ -944,6 +1022,21 @@ describe "Dataset#literal" do
944
1022
  @dataset.literal(:"items__na#m$e").should == "items.na#m$e"
945
1023
  end
946
1024
 
1025
+ specify "should call sql_literal_append with dataset and sql on type if not natively supported and the object responds to it" do
1026
+ @a = Class.new do
1027
+ def sql_literal_append(ds, sql)
1028
+ sql << "called #{ds.blah}"
1029
+ end
1030
+ def sql_literal(ds)
1031
+ "not called #{ds.blah}"
1032
+ end
1033
+ end
1034
+ def @dataset.blah
1035
+ "ds"
1036
+ end
1037
+ @dataset.literal(@a.new).should == "called ds"
1038
+ end
1039
+
947
1040
  specify "should call sql_literal with dataset on type if not natively supported and the object responds to it" do
948
1041
  @a = Class.new do
949
1042
  def sql_literal(ds)
@@ -965,6 +1058,12 @@ describe "Dataset#literal" do
965
1058
  d.literal(d).should == "(#{d.sql})"
966
1059
  end
967
1060
 
1061
+ specify "should literalize Sequel::SQLTime properly" do
1062
+ t = Sequel::SQLTime.now
1063
+ s = t.strftime("'%H:%M:%S")
1064
+ @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
1065
+ end
1066
+
968
1067
  specify "should literalize Time properly" do
969
1068
  t = Time.now
970
1069
  s = t.strftime("'%Y-%m-%d %H:%M:%S")
@@ -1041,10 +1140,9 @@ describe "Dataset#literal" do
1041
1140
  end
1042
1141
 
1043
1142
  specify "should not modify literal strings" do
1143
+ @dataset.quote_identifiers = true
1044
1144
  @dataset.literal('col1 + 2'.lit).should == 'col1 + 2'
1045
-
1046
- @dataset.update_sql(:a => 'a + 2'.lit).should ==
1047
- 'UPDATE test SET a = a + 2'
1145
+ @dataset.update_sql(Sequel::SQL::Identifier.new('a'.lit) => 'a + 2'.lit).should == 'UPDATE "test" SET a = a + 2'
1048
1146
  end
1049
1147
 
1050
1148
  specify "should literalize BigDecimal instances correctly" do
@@ -1054,6 +1152,17 @@ describe "Dataset#literal" do
1054
1152
  @dataset.literal(BigDecimal.new("-Infinity")).should == "'-Infinity'"
1055
1153
  end
1056
1154
 
1155
+ specify "should literalize PlaceholderLiteralStrings correctly" do
1156
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new('? = ?', [1, 2])).should == '1 = 2'
1157
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new('? = ?', [1, 2], true)).should == '(1 = 2)'
1158
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(':a = :b', :a=>1, :b=>2)).should == '1 = 2'
1159
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(':a = :b', {:a=>1, :b=>2}, true)).should == '(1 = 2)'
1160
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(['', ' = ', ''], [1, 2])).should == '1 = 2'
1161
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(['', ' = ', ''], [1, 2], true)).should == '(1 = 2)'
1162
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(['', ' = '], [1, 2])).should == '1 = 2'
1163
+ @dataset.literal(Sequel::SQL::PlaceholderLiteralString.new(['', ' = '], [1, 2], true)).should == '(1 = 2)'
1164
+ end
1165
+
1057
1166
  specify "should raise an Error if the object can't be literalized" do
1058
1167
  proc{@dataset.literal(Object.new)}.should raise_error(Sequel::Error)
1059
1168
  end
@@ -1069,35 +1178,36 @@ describe "Dataset#from" do
1069
1178
  end
1070
1179
 
1071
1180
  specify "should format a Dataset as a subquery if it has had options set" do
1072
- @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
1073
- "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) AS t1"
1181
+ @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should == "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) AS t1"
1074
1182
  end
1075
1183
 
1076
1184
  specify "should automatically alias sub-queries" do
1077
- @dataset.from(@dataset.from(:a).group(:b)).select_sql.should ==
1078
- "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1"
1185
+ @dataset.from(@dataset.from(:a).group(:b)).select_sql.should == "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1"
1079
1186
 
1080
1187
  d1 = @dataset.from(:a).group(:b)
1081
1188
  d2 = @dataset.from(:c).group(:d)
1082
-
1083
- @dataset.from(d1, d2).sql.should ==
1084
- "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1, (SELECT * FROM c GROUP BY d) AS t2"
1189
+ @dataset.from(d1, d2).sql.should == "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1, (SELECT * FROM c GROUP BY d) AS t2"
1085
1190
  end
1086
1191
 
1087
1192
  specify "should accept a hash for aliasing" do
1088
- @dataset.from(:a => :b).sql.should ==
1089
- "SELECT * FROM a AS b"
1090
-
1091
- @dataset.from(:a => 'b').sql.should ==
1092
- "SELECT * FROM a AS b"
1093
-
1094
- @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should ==
1095
- "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
1193
+ @dataset.from(:a => :b).sql.should == "SELECT * FROM a AS b"
1194
+ @dataset.from(:a => 'b').sql.should == "SELECT * FROM a AS b"
1195
+ @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should == "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
1096
1196
  end
1097
1197
 
1098
1198
  specify "should always use a subquery if given a dataset" do
1099
- @dataset.from(@dataset.from(:a)).select_sql.should ==
1100
- "SELECT * FROM (SELECT * FROM a) AS t1"
1199
+ @dataset.from(@dataset.from(:a)).select_sql.should == "SELECT * FROM (SELECT * FROM a) AS t1"
1200
+ end
1201
+
1202
+ specify "should treat string arguments as identifiers" do
1203
+ @dataset.quote_identifiers = true
1204
+ @dataset.from('a').select_sql.should == "SELECT * FROM \"a\""
1205
+ end
1206
+
1207
+ specify "should not treat literal strings or blobs as identifiers" do
1208
+ @dataset.quote_identifiers = true
1209
+ @dataset.from('a'.lit).select_sql.should == "SELECT * FROM a"
1210
+ @dataset.from('a'.to_sequel_blob).select_sql.should == "SELECT * FROM 'a'"
1101
1211
  end
1102
1212
 
1103
1213
  specify "should remove all FROM tables if called with no arguments" do
@@ -1105,32 +1215,31 @@ describe "Dataset#from" do
1105
1215
  end
1106
1216
 
1107
1217
  specify "should accept sql functions" do
1108
- @dataset.from(:abc.sql_function(:def)).select_sql.should ==
1109
- "SELECT * FROM abc(def)"
1110
-
1111
- @dataset.from(:a.sql_function(:i)).select_sql.should ==
1112
- "SELECT * FROM a(i)"
1218
+ @dataset.from(:abc.sql_function(:def)).select_sql.should == "SELECT * FROM abc(def)"
1219
+ @dataset.from(:a.sql_function(:i)).select_sql.should == "SELECT * FROM a(i)"
1113
1220
  end
1114
1221
 
1115
1222
  specify "should accept :schema__table___alias symbol format" do
1116
- @dataset.from(:abc__def).select_sql.should ==
1117
- "SELECT * FROM abc.def"
1118
- @dataset.from(:a_b__c).select_sql.should ==
1119
- "SELECT * FROM a_b.c"
1120
- @dataset.from(:'#__#').select_sql.should ==
1121
- 'SELECT * FROM #.#'
1122
- @dataset.from(:abc__def___d).select_sql.should ==
1123
- "SELECT * FROM abc.def AS d"
1124
- @dataset.from(:a_b__d_e___f_g).select_sql.should ==
1125
- "SELECT * FROM a_b.d_e AS f_g"
1126
- @dataset.from(:'#__#___#').select_sql.should ==
1127
- 'SELECT * FROM #.# AS #'
1128
- @dataset.from(:abc___def).select_sql.should ==
1129
- "SELECT * FROM abc AS def"
1130
- @dataset.from(:a_b___c_d).select_sql.should ==
1131
- "SELECT * FROM a_b AS c_d"
1132
- @dataset.from(:'#___#').select_sql.should ==
1133
- 'SELECT * FROM # AS #'
1223
+ @dataset.from(:abc__def).select_sql.should == "SELECT * FROM abc.def"
1224
+ @dataset.from(:a_b__c).select_sql.should == "SELECT * FROM a_b.c"
1225
+ @dataset.from(:'#__#').select_sql.should == 'SELECT * FROM #.#'
1226
+ @dataset.from(:abc__def___d).select_sql.should == "SELECT * FROM abc.def AS d"
1227
+ @dataset.from(:a_b__d_e___f_g).select_sql.should == "SELECT * FROM a_b.d_e AS f_g"
1228
+ @dataset.from(:'#__#___#').select_sql.should == 'SELECT * FROM #.# AS #'
1229
+ @dataset.from(:abc___def).select_sql.should == "SELECT * FROM abc AS def"
1230
+ @dataset.from(:a_b___c_d).select_sql.should == "SELECT * FROM a_b AS c_d"
1231
+ @dataset.from(:'#___#').select_sql.should == 'SELECT * FROM # AS #'
1232
+ end
1233
+
1234
+ specify "should not handle :foo__schema__table___alias specially" do
1235
+ @dataset.from(:foo__schema__table___alias).select_sql.should == "SELECT * FROM foo.schema__table AS alias"
1236
+ end
1237
+
1238
+ specify "should hoist WITH clauses from subqueries if the dataset doesn't support CTEs in subselects" do
1239
+ @dataset.meta_def(:supports_cte?){true}
1240
+ @dataset.meta_def(:supports_cte_in_subselect?){false}
1241
+ @dataset.from(@dataset.from(:a).with(:a, @dataset.from(:b))).sql.should == 'WITH a AS (SELECT * FROM b) SELECT * FROM (SELECT * FROM a) AS t1'
1242
+ @dataset.from(@dataset.from(:a).with(:a, @dataset.from(:b)), @dataset.from(:c).with(:c, @dataset.from(:d))).sql.should == 'WITH a AS (SELECT * FROM b), c AS (SELECT * FROM d) SELECT * FROM (SELECT * FROM a) AS t1, (SELECT * FROM c) AS t2'
1134
1243
  end
1135
1244
  end
1136
1245
 
@@ -1147,39 +1256,39 @@ describe "Dataset#select" do
1147
1256
  specify "should accept symbols and literal strings" do
1148
1257
  @d.select('aaa'.lit).sql.should == 'SELECT aaa FROM test'
1149
1258
  @d.select(:a, 'b'.lit).sql.should == 'SELECT a, b FROM test'
1150
- @d.select(:test__cc, 'test.d AS e'.lit).sql.should ==
1151
- 'SELECT test.cc, test.d AS e FROM test'
1152
- @d.select('test.d AS e'.lit, :test__cc).sql.should ==
1153
- 'SELECT test.d AS e, test.cc FROM test'
1154
-
1155
- # symbol helpers
1156
- @d.select(:test.*).sql.should ==
1157
- 'SELECT test.* FROM test'
1158
- @d.select(:test__name.as(:n)).sql.should ==
1159
- 'SELECT test.name AS n FROM test'
1160
- @d.select(:test__name___n).sql.should ==
1161
- 'SELECT test.name AS n FROM test'
1259
+ @d.select(:test__cc, 'test.d AS e'.lit).sql.should == 'SELECT test.cc, test.d AS e FROM test'
1260
+ @d.select('test.d AS e'.lit, :test__cc).sql.should == 'SELECT test.d AS e, test.cc FROM test'
1261
+ @d.select(:test__name___n).sql.should == 'SELECT test.name AS n FROM test'
1262
+ end
1263
+
1264
+ specify "should accept ColumnAlls" do
1265
+ @d.select(:test.*).sql.should == 'SELECT test.* FROM test'
1266
+ end
1267
+
1268
+ specify "should accept QualifiedIdentifiers" do
1269
+ @d.select(:test__name.as(:n)).sql.should == 'SELECT test.name AS n FROM test'
1162
1270
  end
1163
1271
 
1164
1272
  specify "should use the wildcard if no arguments are given" do
1165
1273
  @d.select.sql.should == 'SELECT * FROM test'
1166
1274
  end
1167
1275
 
1276
+ specify "should handle array condition specifiers that are aliased" do
1277
+ @d.select(Sequel.as([[:b, :c]], :n)).sql.should == 'SELECT (b = c) AS n FROM test'
1278
+ end
1279
+
1168
1280
  specify "should accept a hash for AS values" do
1169
- @d.select(:name => 'n', :__ggh => 'age').sql.should =~
1170
- /SELECT ((name AS n, __ggh AS age)|(__ggh AS age, name AS n)) FROM test/
1281
+ @d.select(:name => 'n', :__ggh => 'age').sql.should =~ /SELECT ((name AS n, __ggh AS age)|(__ggh AS age, name AS n)) FROM test/
1171
1282
  end
1172
1283
 
1173
- specify "should overrun the previous select option" do
1284
+ specify "should override the previous select option" do
1174
1285
  @d.select!(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
1175
1286
  @d.select!(:price).select(:name).sql.should == 'SELECT name FROM test'
1176
1287
  end
1177
1288
 
1178
1289
  specify "should accept arbitrary objects and literalize them correctly" do
1179
1290
  @d.select(1, :a, 't').sql.should == "SELECT 1, a, 't' FROM test"
1180
-
1181
1291
  @d.select(nil, :sum.sql_function(:t), :x___y).sql.should == "SELECT NULL, sum(t), x AS y FROM test"
1182
-
1183
1292
  @d.select(nil, 1, :x => :y).sql.should == "SELECT NULL, 1, x AS y FROM test"
1184
1293
  end
1185
1294
 
@@ -1198,6 +1307,27 @@ describe "Dataset#select" do
1198
1307
  end
1199
1308
  end
1200
1309
 
1310
+ describe "Dataset#select_group" do
1311
+ before do
1312
+ @d = Sequel::Dataset.new(nil).from(:test)
1313
+ end
1314
+
1315
+ specify "should set both SELECT and GROUP" do
1316
+ @d.select_group(:name).sql.should == 'SELECT name FROM test GROUP BY name'
1317
+ @d.select_group(:a, :b__c, :d___e).sql.should == 'SELECT a, b.c, d AS e FROM test GROUP BY a, b.c, d'
1318
+ end
1319
+
1320
+ specify "should remove from both SELECT and GROUP if no arguments" do
1321
+ @d.select_group(:name).select_group.sql.should == 'SELECT * FROM test'
1322
+ end
1323
+
1324
+ specify "should accept virtual row blocks" do
1325
+ @d.select_group{name}.sql.should == 'SELECT name FROM test GROUP BY name'
1326
+ @d.select_group{[name, f(v).as(a)]}.sql.should == 'SELECT name, f(v) AS a FROM test GROUP BY name, f(v)'
1327
+ @d.select_group(:name){f(v).as(a)}.sql.should == 'SELECT name, f(v) AS a FROM test GROUP BY name, f(v)'
1328
+ end
1329
+ end
1330
+
1201
1331
  describe "Dataset#select_all" do
1202
1332
  before do
1203
1333
  @d = Sequel::Dataset.new(nil).from(:test)
@@ -1207,9 +1337,43 @@ describe "Dataset#select_all" do
1207
1337
  @d.select_all.sql.should == 'SELECT * FROM test'
1208
1338
  end
1209
1339
 
1210
- specify "should overrun the previous select option" do
1340
+ specify "should override the previous select option" do
1211
1341
  @d.select!(:a, :b, :c).select_all.sql.should == 'SELECT * FROM test'
1212
1342
  end
1343
+
1344
+ specify "should select all columns in a table if given an argument" do
1345
+ @d.select_all(:test).sql.should == 'SELECT test.* FROM test'
1346
+ end
1347
+
1348
+ specify "should select all columns all tables if given a multiple arguments" do
1349
+ @d.select_all(:test, :foo).sql.should == 'SELECT test.*, foo.* FROM test'
1350
+ end
1351
+
1352
+ specify "should work correctly with qualified symbols" do
1353
+ @d.select_all(:sch__test).sql.should == 'SELECT sch.test.* FROM test'
1354
+ end
1355
+
1356
+ specify "should work correctly with aliased symbols" do
1357
+ @d.select_all(:test___al).sql.should == 'SELECT al.* FROM test'
1358
+ @d.select_all(:sch__test___al).sql.should == 'SELECT al.* FROM test'
1359
+ end
1360
+
1361
+ specify "should work correctly with SQL::Identifiers" do
1362
+ @d.select_all(:test.identifier).sql.should == 'SELECT test.* FROM test'
1363
+ end
1364
+
1365
+ specify "should work correctly with SQL::QualifiedIdentifier" do
1366
+ @d.select_all(:test.qualify(:sch)).sql.should == 'SELECT sch.test.* FROM test'
1367
+ end
1368
+
1369
+ specify "should work correctly with SQL::AliasedExpressions" do
1370
+ @d.select_all(:test.as(:al)).sql.should == 'SELECT al.* FROM test'
1371
+ end
1372
+
1373
+ specify "should work correctly with SQL::JoinClauses" do
1374
+ d = @d.cross_join(:foo).cross_join(:test___al)
1375
+ @d.select_all(*d.opts[:join]).sql.should == 'SELECT foo.*, al.* FROM test'
1376
+ end
1213
1377
  end
1214
1378
 
1215
1379
  describe "Dataset#select_more" do
@@ -1254,6 +1418,14 @@ describe "Dataset#select_append" do
1254
1418
  @d.select(:a).select_append{|o| o.b}.sql.should == 'SELECT a, b FROM test'
1255
1419
  @d.select(:a.*).select_append(:b.*){b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
1256
1420
  end
1421
+
1422
+ specify "should select from all from and join tables if SELECT *, column not supported" do
1423
+ @d.meta_def(:supports_select_all_and_column?){false}
1424
+ @d.select_append(:b).sql.should == 'SELECT test.*, b FROM test'
1425
+ @d.from(:test, :c).select_append(:b).sql.should == 'SELECT test.*, c.*, b FROM test, c'
1426
+ @d.cross_join(:c).select_append(:b).sql.should == 'SELECT test.*, c.*, b FROM test CROSS JOIN c'
1427
+ @d.cross_join(:c).cross_join(:d).select_append(:b).sql.should == 'SELECT test.*, c.*, d.*, b FROM test CROSS JOIN c CROSS JOIN d'
1428
+ end
1257
1429
  end
1258
1430
 
1259
1431
  describe "Dataset#order" do
@@ -1262,38 +1434,31 @@ describe "Dataset#order" do
1262
1434
  end
1263
1435
 
1264
1436
  specify "should include an ORDER BY clause in the select statement" do
1265
- @dataset.order(:name).sql.should ==
1266
- 'SELECT * FROM test ORDER BY name'
1437
+ @dataset.order(:name).sql.should == 'SELECT * FROM test ORDER BY name'
1267
1438
  end
1268
1439
 
1269
1440
  specify "should accept multiple arguments" do
1270
- @dataset.order(:name, :price.desc).sql.should ==
1271
- 'SELECT * FROM test ORDER BY name, price DESC'
1441
+ @dataset.order(:name, :price.desc).sql.should == 'SELECT * FROM test ORDER BY name, price DESC'
1272
1442
  end
1273
1443
 
1274
1444
  specify "should accept :nulls options for asc and desc" do
1275
- @dataset.order(:name.asc(:nulls=>:last), :price.desc(:nulls=>:first)).sql.should ==
1276
- 'SELECT * FROM test ORDER BY name ASC NULLS LAST, price DESC NULLS FIRST'
1445
+ @dataset.order(:name.asc(:nulls=>:last), :price.desc(:nulls=>:first)).sql.should == 'SELECT * FROM test ORDER BY name ASC NULLS LAST, price DESC NULLS FIRST'
1277
1446
  end
1278
1447
 
1279
- specify "should overrun a previous ordering" do
1280
- @dataset.order(:name).order(:stamp).sql.should ==
1281
- 'SELECT * FROM test ORDER BY stamp'
1448
+ specify "should override a previous ordering" do
1449
+ @dataset.order(:name).order(:stamp).sql.should == 'SELECT * FROM test ORDER BY stamp'
1282
1450
  end
1283
1451
 
1284
1452
  specify "should accept a literal string" do
1285
- @dataset.order('dada ASC'.lit).sql.should ==
1286
- 'SELECT * FROM test ORDER BY dada ASC'
1453
+ @dataset.order('dada ASC'.lit).sql.should == 'SELECT * FROM test ORDER BY dada ASC'
1287
1454
  end
1288
1455
 
1289
1456
  specify "should accept a hash as an expression" do
1290
- @dataset.order(:name=>nil).sql.should ==
1291
- 'SELECT * FROM test ORDER BY (name IS NULL)'
1457
+ @dataset.order(:name=>nil).sql.should == 'SELECT * FROM test ORDER BY (name IS NULL)'
1292
1458
  end
1293
1459
 
1294
1460
  specify "should accept a nil to remove ordering" do
1295
- @dataset.order(:bah).order(nil).sql.should ==
1296
- 'SELECT * FROM test'
1461
+ @dataset.order(:bah).order(nil).sql.should == 'SELECT * FROM test'
1297
1462
  end
1298
1463
 
1299
1464
  specify "should accept a block that yields a virtual row" do
@@ -1355,6 +1520,13 @@ describe "Dataset#with_sql" do
1355
1520
  specify "should keep row_proc" do
1356
1521
  @dataset.with_sql('SELECT 1 FROM test').row_proc.should == @dataset.row_proc
1357
1522
  end
1523
+
1524
+ specify "should work with method symbols and arguments" do
1525
+ @dataset.with_sql(:delete_sql).sql.should == 'DELETE FROM test'
1526
+ @dataset.with_sql(:insert_sql, :b=>1).sql.should == 'INSERT INTO test (b) VALUES (1)'
1527
+ @dataset.with_sql(:update_sql, :b=>1).sql.should == 'UPDATE test SET b = 1'
1528
+ end
1529
+
1358
1530
  end
1359
1531
 
1360
1532
  describe "Dataset#order_by" do
@@ -1363,28 +1535,23 @@ describe "Dataset#order_by" do
1363
1535
  end
1364
1536
 
1365
1537
  specify "should include an ORDER BY clause in the select statement" do
1366
- @dataset.order_by(:name).sql.should ==
1367
- 'SELECT * FROM test ORDER BY name'
1538
+ @dataset.order_by(:name).sql.should == 'SELECT * FROM test ORDER BY name'
1368
1539
  end
1369
1540
 
1370
1541
  specify "should accept multiple arguments" do
1371
- @dataset.order_by(:name, :price.desc).sql.should ==
1372
- 'SELECT * FROM test ORDER BY name, price DESC'
1542
+ @dataset.order_by(:name, :price.desc).sql.should == 'SELECT * FROM test ORDER BY name, price DESC'
1373
1543
  end
1374
1544
 
1375
- specify "should overrun a previous ordering" do
1376
- @dataset.order_by(:name).order(:stamp).sql.should ==
1377
- 'SELECT * FROM test ORDER BY stamp'
1545
+ specify "should override a previous ordering" do
1546
+ @dataset.order_by(:name).order(:stamp).sql.should == 'SELECT * FROM test ORDER BY stamp'
1378
1547
  end
1379
1548
 
1380
1549
  specify "should accept a string" do
1381
- @dataset.order_by('dada ASC'.lit).sql.should ==
1382
- 'SELECT * FROM test ORDER BY dada ASC'
1550
+ @dataset.order_by('dada ASC'.lit).sql.should == 'SELECT * FROM test ORDER BY dada ASC'
1383
1551
  end
1384
1552
 
1385
1553
  specify "should accept a nil to remove ordering" do
1386
- @dataset.order_by(:bah).order_by(nil).sql.should ==
1387
- 'SELECT * FROM test'
1554
+ @dataset.order_by(:bah).order_by(nil).sql.should == 'SELECT * FROM test'
1388
1555
  end
1389
1556
  end
1390
1557
 
@@ -1417,13 +1584,11 @@ describe "Dataset#order_prepend" do
1417
1584
  end
1418
1585
 
1419
1586
  specify "should include an ORDER BY clause in the select statement" do
1420
- @dataset.order_prepend(:name).sql.should ==
1421
- 'SELECT * FROM test ORDER BY name'
1587
+ @dataset.order_prepend(:name).sql.should == 'SELECT * FROM test ORDER BY name'
1422
1588
  end
1423
1589
 
1424
1590
  specify "should add to the beginning of a previous ordering" do
1425
- @dataset.order(:name).order_prepend(:stamp.desc).sql.should ==
1426
- 'SELECT * FROM test ORDER BY stamp DESC, name'
1591
+ @dataset.order(:name).order_prepend(:stamp.desc).sql.should == 'SELECT * FROM test ORDER BY stamp DESC, name'
1427
1592
  end
1428
1593
 
1429
1594
  specify "should accept a block that yields a virtual row" do
@@ -1438,45 +1603,36 @@ describe "Dataset#reverse_order" do
1438
1603
  end
1439
1604
 
1440
1605
  specify "should use DESC as default order" do
1441
- @dataset.reverse_order(:name).sql.should ==
1442
- 'SELECT * FROM test ORDER BY name DESC'
1606
+ @dataset.reverse_order(:name).sql.should == 'SELECT * FROM test ORDER BY name DESC'
1443
1607
  end
1444
1608
 
1445
1609
  specify "should invert the order given" do
1446
- @dataset.reverse_order(:name.desc).sql.should ==
1447
- 'SELECT * FROM test ORDER BY name ASC'
1610
+ @dataset.reverse_order(:name.desc).sql.should == 'SELECT * FROM test ORDER BY name ASC'
1448
1611
  end
1449
1612
 
1450
1613
  specify "should invert the order for ASC expressions" do
1451
- @dataset.reverse_order(:name.asc).sql.should ==
1452
- 'SELECT * FROM test ORDER BY name DESC'
1614
+ @dataset.reverse_order(:name.asc).sql.should == 'SELECT * FROM test ORDER BY name DESC'
1453
1615
  end
1454
1616
 
1455
1617
  specify "should accept multiple arguments" do
1456
- @dataset.reverse_order(:name, :price.desc).sql.should ==
1457
- 'SELECT * FROM test ORDER BY name DESC, price ASC'
1618
+ @dataset.reverse_order(:name, :price.desc).sql.should == 'SELECT * FROM test ORDER BY name DESC, price ASC'
1458
1619
  end
1459
1620
 
1460
1621
  specify "should handles NULLS ordering correctly when reversing" do
1461
- @dataset.reverse_order(:name.asc(:nulls=>:first), :price.desc(:nulls=>:last)).sql.should ==
1462
- 'SELECT * FROM test ORDER BY name DESC NULLS LAST, price ASC NULLS FIRST'
1622
+ @dataset.reverse_order(:name.asc(:nulls=>:first), :price.desc(:nulls=>:last)).sql.should == 'SELECT * FROM test ORDER BY name DESC NULLS LAST, price ASC NULLS FIRST'
1463
1623
  end
1464
1624
 
1465
1625
  specify "should reverse a previous ordering if no arguments are given" do
1466
- @dataset.order(:name).reverse_order.sql.should ==
1467
- 'SELECT * FROM test ORDER BY name DESC'
1468
- @dataset.order(:clumsy.desc, :fool).reverse_order.sql.should ==
1469
- 'SELECT * FROM test ORDER BY clumsy ASC, fool DESC'
1626
+ @dataset.order(:name).reverse_order.sql.should == 'SELECT * FROM test ORDER BY name DESC'
1627
+ @dataset.order(:clumsy.desc, :fool).reverse_order.sql.should == 'SELECT * FROM test ORDER BY clumsy ASC, fool DESC'
1470
1628
  end
1471
1629
 
1472
1630
  specify "should return an unordered dataset for a dataset with no order" do
1473
- @dataset.unordered.reverse_order.sql.should ==
1474
- 'SELECT * FROM test'
1631
+ @dataset.unordered.reverse_order.sql.should == 'SELECT * FROM test'
1475
1632
  end
1476
1633
 
1477
1634
  specify "should have #reverse alias" do
1478
- @dataset.order(:name).reverse.sql.should ==
1479
- 'SELECT * FROM test ORDER BY name DESC'
1635
+ @dataset.order(:name).reverse.sql.should == 'SELECT * FROM test ORDER BY name DESC'
1480
1636
  end
1481
1637
  end
1482
1638
 
@@ -1486,42 +1642,33 @@ describe "Dataset#limit" do
1486
1642
  end
1487
1643
 
1488
1644
  specify "should include a LIMIT clause in the select statement" do
1489
- @dataset.limit(10).sql.should ==
1490
- 'SELECT * FROM test LIMIT 10'
1645
+ @dataset.limit(10).sql.should == 'SELECT * FROM test LIMIT 10'
1491
1646
  end
1492
1647
 
1493
1648
  specify "should accept ranges" do
1494
- @dataset.limit(3..7).sql.should ==
1495
- 'SELECT * FROM test LIMIT 5 OFFSET 3'
1496
-
1497
- @dataset.limit(3...7).sql.should ==
1498
- 'SELECT * FROM test LIMIT 4 OFFSET 3'
1649
+ @dataset.limit(3..7).sql.should == 'SELECT * FROM test LIMIT 5 OFFSET 3'
1650
+ @dataset.limit(3...7).sql.should == 'SELECT * FROM test LIMIT 4 OFFSET 3'
1499
1651
  end
1500
1652
 
1501
1653
  specify "should include an offset if a second argument is given" do
1502
- @dataset.limit(6, 10).sql.should ==
1503
- 'SELECT * FROM test LIMIT 6 OFFSET 10'
1504
- end
1654
+ @dataset.limit(6, 10).sql.should == 'SELECT * FROM test LIMIT 6 OFFSET 10'
1655
+ end
1505
1656
 
1506
1657
  specify "should convert regular strings to integers" do
1507
- @dataset.limit('6', 'a() - 1').sql.should ==
1508
- 'SELECT * FROM test LIMIT 6 OFFSET 0'
1658
+ @dataset.limit('6', 'a() - 1').sql.should == 'SELECT * FROM test LIMIT 6 OFFSET 0'
1509
1659
  end
1510
1660
 
1511
1661
  specify "should not convert literal strings to integers" do
1512
- @dataset.limit('6'.lit, 'a() - 1'.lit).sql.should ==
1513
- 'SELECT * FROM test LIMIT 6 OFFSET a() - 1'
1662
+ @dataset.limit('6'.lit, 'a() - 1'.lit).sql.should == 'SELECT * FROM test LIMIT 6 OFFSET a() - 1'
1514
1663
  end
1515
1664
 
1516
1665
  specify "should not convert other objects" do
1517
- @dataset.limit(6, :a.sql_function - 1).sql.should ==
1518
- 'SELECT * FROM test LIMIT 6 OFFSET (a() - 1)'
1666
+ @dataset.limit(6, :a.sql_function - 1).sql.should == 'SELECT * FROM test LIMIT 6 OFFSET (a() - 1)'
1519
1667
  end
1520
1668
 
1521
1669
  specify "should work with fixed sql datasets" do
1522
1670
  @dataset.opts[:sql] = 'select * from cccc'
1523
- @dataset.limit(6, 10).sql.should ==
1524
- 'SELECT * FROM (select * from cccc) AS t1 LIMIT 6 OFFSET 10'
1671
+ @dataset.limit(6, 10).sql.should == 'SELECT * FROM (select * from cccc) AS t1 LIMIT 6 OFFSET 10'
1525
1672
  end
1526
1673
 
1527
1674
  specify "should raise an error if an invalid limit or offset is used" do
@@ -1535,15 +1682,20 @@ describe "Dataset#limit" do
1535
1682
  end
1536
1683
 
1537
1684
  describe "Dataset#naked" do
1538
- before do
1539
- @d1 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4})
1540
- @d2 = @d1.clone
1541
- @d2.row_proc = Proc.new{|r| r}
1685
+ specify "should returned clone dataset without row_proc" do
1686
+ d = Sequel::Dataset.new(nil)
1687
+ d.row_proc = Proc.new{|r| r}
1688
+ d.naked.row_proc.should be_nil
1689
+ d.row_proc.should_not be_nil
1542
1690
  end
1543
-
1691
+ end
1692
+
1693
+ describe "Dataset#naked!" do
1544
1694
  specify "should remove any existing row_proc" do
1545
- naked = @d2.naked
1546
- naked.row_proc.should be_nil
1695
+ d = Sequel::Dataset.new(nil)
1696
+ d.row_proc = Proc.new{|r| r}
1697
+ d.naked!.row_proc.should be_nil
1698
+ d.row_proc.should be_nil
1547
1699
  end
1548
1700
  end
1549
1701
 
@@ -1571,38 +1723,42 @@ describe "Dataset#qualified_column_name" do
1571
1723
  end
1572
1724
  end
1573
1725
 
1574
- class DummyDataset < Sequel::Dataset
1575
- VALUES = [
1576
- {:a => 1, :b => 2},
1577
- {:a => 3, :b => 4},
1578
- {:a => 5, :b => 6}
1579
- ]
1580
- def fetch_rows(sql, &block)
1581
- VALUES.each(&block)
1582
- end
1583
- end
1584
-
1585
1726
  describe "Dataset#map" do
1586
1727
  before do
1587
- @d = DummyDataset.new(nil).from(:items)
1728
+ @d = Sequel.mock(:fetch=>[{:a => 1, :b => 2}, {:a => 3, :b => 4}, {:a => 5, :b => 6}])[:items]
1588
1729
  end
1589
1730
 
1590
1731
  specify "should provide the usual functionality if no argument is given" do
1591
- @d.map {|n| n[:a] + n[:b]}.should == [3, 7, 11]
1732
+ @d.map{|n| n[:a] + n[:b]}.should == [3, 7, 11]
1592
1733
  end
1593
1734
 
1594
1735
  specify "should map using #[column name] if column name is given" do
1595
1736
  @d.map(:a).should == [1, 3, 5]
1596
1737
  end
1597
1738
 
1739
+ specify "should support multiple column names if an array of column names is given" do
1740
+ @d.map([:a, :b]).should == [[1, 2], [3, 4], [5, 6]]
1741
+ end
1742
+
1743
+ specify "should not call the row_proc if an argument is given" do
1744
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1745
+ @d.map(:a).should == [1, 3, 5]
1746
+ @d.map([:a, :b]).should == [[1, 2], [3, 4], [5, 6]]
1747
+ end
1748
+
1749
+ specify "should call the row_proc if no argument is given" do
1750
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1751
+ @d.map{|n| n[:a] + n[:b]}.should == [6, 14, 22]
1752
+ end
1753
+
1598
1754
  specify "should return the complete dataset values if nothing is given" do
1599
- @d.map.to_a.should == DummyDataset::VALUES
1755
+ @d.map.to_a.should == [{:a => 1, :b => 2}, {:a => 3, :b => 4}, {:a => 5, :b => 6}]
1600
1756
  end
1601
1757
  end
1602
1758
 
1603
1759
  describe "Dataset#to_hash" do
1604
1760
  before do
1605
- @d = DummyDataset.new(nil).from(:items)
1761
+ @d = Sequel.mock(:fetch=>[{:a => 1, :b => 2}, {:a => 3, :b => 4}, {:a => 5, :b => 6}])[:items]
1606
1762
  end
1607
1763
 
1608
1764
  specify "should provide a hash with the first column as key and the second as value" do
@@ -1614,11 +1770,73 @@ describe "Dataset#to_hash" do
1614
1770
  @d.to_hash(:a).should == {1 => {:a => 1, :b => 2}, 3 => {:a => 3, :b => 4}, 5 => {:a => 5, :b => 6}}
1615
1771
  @d.to_hash(:b).should == {2 => {:a => 1, :b => 2}, 4 => {:a => 3, :b => 4}, 6 => {:a => 5, :b => 6}}
1616
1772
  end
1773
+
1774
+ specify "should support using an array of columns as either the key or the value" do
1775
+ @d.to_hash([:a, :b], :b).should == {[1, 2] => 2, [3, 4] => 4, [5, 6] => 6}
1776
+ @d.to_hash(:b, [:a, :b]).should == {2 => [1, 2], 4 => [3, 4], 6 => [5, 6]}
1777
+ @d.to_hash([:b, :a], [:a, :b]).should == {[2, 1] => [1, 2], [4, 3] => [3, 4], [6, 5] => [5, 6]}
1778
+ @d.to_hash([:a, :b]).should == {[1, 2] => {:a => 1, :b => 2}, [3, 4] => {:a => 3, :b => 4}, [5, 6] => {:a => 5, :b => 6}}
1779
+ end
1780
+
1781
+ specify "should not call the row_proc if two arguments are given" do
1782
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1783
+ @d.to_hash(:a, :b).should == {1 => 2, 3 => 4, 5 => 6}
1784
+ @d.to_hash(:b, :a).should == {2 => 1, 4 => 3, 6 => 5}
1785
+ @d.to_hash([:a, :b], :b).should == {[1, 2] => 2, [3, 4] => 4, [5, 6] => 6}
1786
+ @d.to_hash(:b, [:a, :b]).should == {2 => [1, 2], 4 => [3, 4], 6 => [5, 6]}
1787
+ @d.to_hash([:b, :a], [:a, :b]).should == {[2, 1] => [1, 2], [4, 3] => [3, 4], [6, 5] => [5, 6]}
1788
+ end
1789
+
1790
+ specify "should call the row_proc if only a single argument is given" do
1791
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1792
+ @d.to_hash(:a).should == {2 => {:a => 2, :b => 4}, 6 => {:a => 6, :b => 8}, 10 => {:a => 10, :b => 12}}
1793
+ @d.to_hash(:b).should == {4 => {:a => 2, :b => 4}, 8 => {:a => 6, :b => 8}, 12 => {:a => 10, :b => 12}}
1794
+ @d.to_hash([:a, :b]).should == {[2, 4] => {:a => 2, :b => 4}, [6, 8] => {:a => 6, :b => 8}, [10, 12] => {:a => 10, :b => 12}}
1795
+ end
1796
+ end
1797
+
1798
+ describe "Dataset#to_hash_groups" do
1799
+ before do
1800
+ @d = Sequel.mock(:fetch=>[{:a => 1, :b => 2}, {:a => 3, :b => 4}, {:a => 1, :b => 6}, {:a => 7, :b => 4}])[:items]
1801
+ end
1802
+
1803
+ specify "should provide a hash with the first column as key and the second as value" do
1804
+ @d.to_hash_groups(:a, :b).should == {1 => [2, 6], 3 => [4], 7 => [4]}
1805
+ @d.to_hash_groups(:b, :a).should == {2 => [1], 4=>[3, 7], 6=>[1]}
1806
+ end
1807
+
1808
+ 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
1809
+ @d.to_hash_groups(:a).should == {1 => [{:a => 1, :b => 2}, {:a => 1, :b => 6}], 3 => [{:a => 3, :b => 4}], 7 => [{:a => 7, :b => 4}]}
1810
+ @d.to_hash_groups(:b).should == {2 => [{:a => 1, :b => 2}], 4 => [{:a => 3, :b => 4}, {:a => 7, :b => 4}], 6 => [{:a => 1, :b => 6}]}
1811
+ end
1812
+
1813
+ specify "should support using an array of columns as either the key or the value" do
1814
+ @d.to_hash_groups([:a, :b], :b).should == {[1, 2] => [2], [3, 4] => [4], [1, 6] => [6], [7, 4]=>[4]}
1815
+ @d.to_hash_groups(:b, [:a, :b]).should == {2 => [[1, 2]], 4 => [[3, 4], [7, 4]], 6 => [[1, 6]]}
1816
+ @d.to_hash_groups([:b, :a], [:a, :b]).should == {[2, 1] => [[1, 2]], [4, 3] => [[3, 4]], [6, 1] => [[1, 6]], [4, 7]=>[[7, 4]]}
1817
+ @d.to_hash_groups([:a, :b]).should == {[1, 2] => [{:a => 1, :b => 2}], [3, 4] => [{:a => 3, :b => 4}], [1, 6] => [{:a => 1, :b => 6}], [7, 4] => [{:a => 7, :b => 4}]}
1818
+ end
1819
+
1820
+ specify "should not call the row_proc if two arguments are given" do
1821
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1822
+ @d.to_hash_groups(:a, :b).should == {1 => [2, 6], 3 => [4], 7 => [4]}
1823
+ @d.to_hash_groups(:b, :a).should == {2 => [1], 4=>[3, 7], 6=>[1]}
1824
+ @d.to_hash_groups([:a, :b], :b).should == {[1, 2] => [2], [3, 4] => [4], [1, 6] => [6], [7, 4]=>[4]}
1825
+ @d.to_hash_groups(:b, [:a, :b]).should == {2 => [[1, 2]], 4 => [[3, 4], [7, 4]], 6 => [[1, 6]]}
1826
+ @d.to_hash_groups([:b, :a], [:a, :b]).should == {[2, 1] => [[1, 2]], [4, 3] => [[3, 4]], [6, 1] => [[1, 6]], [4, 7]=>[[7, 4]]}
1827
+ end
1828
+
1829
+ specify "should call the row_proc if only a single argument is given" do
1830
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1831
+ @d.to_hash_groups(:a).should == {2 => [{:a => 2, :b => 4}, {:a => 2, :b => 12}], 6 => [{:a => 6, :b => 8}], 14 => [{:a => 14, :b => 8}]}
1832
+ @d.to_hash_groups(:b).should == {4 => [{:a => 2, :b => 4}], 8 => [{:a => 6, :b => 8}, {:a => 14, :b => 8}], 12 => [{:a => 2, :b => 12}]}
1833
+ @d.to_hash_groups([:a, :b]).should == {[2, 4] => [{:a => 2, :b => 4}], [6, 8] => [{:a => 6, :b => 8}], [2, 12] => [{:a => 2, :b => 12}], [14, 8] => [{:a => 14, :b => 8}]}
1834
+ end
1617
1835
  end
1618
1836
 
1619
1837
  describe "Dataset#distinct" do
1620
1838
  before do
1621
- @db = MockDatabase.new
1839
+ @db = Sequel.mock
1622
1840
  @dataset = @db[:test].select(:name)
1623
1841
  end
1624
1842
 
@@ -1645,132 +1863,100 @@ end
1645
1863
 
1646
1864
  describe "Dataset#count" do
1647
1865
  before do
1648
- @c = Class.new(Sequel::Dataset) do
1649
- def self.sql
1650
- @@sql
1651
- end
1652
-
1653
- def fetch_rows(sql)
1654
- @columns = [sql =~ /SELECT COUNT/i ? :count : :a]
1655
- @@sql = sql
1656
- yield({@columns.first=>1})
1657
- end
1658
- end
1659
- @dataset = @c.new(nil).from(:test)
1866
+ @db = Sequel.mock(:fetch=>{:count=>1})
1867
+ @dataset = @db.from(:test).columns(:count)
1660
1868
  end
1661
1869
 
1662
1870
  specify "should format SQL properly" do
1663
1871
  @dataset.count.should == 1
1664
- @c.sql.should == 'SELECT COUNT(*) AS count FROM test LIMIT 1'
1872
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM test LIMIT 1']
1665
1873
  end
1666
1874
 
1667
1875
  specify "should include the where clause if it's there" do
1668
1876
  @dataset.filter(:abc.sql_number < 30).count.should == 1
1669
- @c.sql.should == 'SELECT COUNT(*) AS count FROM test WHERE (abc < 30) LIMIT 1'
1877
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM test WHERE (abc < 30) LIMIT 1']
1670
1878
  end
1671
1879
 
1672
1880
  specify "should count properly for datasets with fixed sql" do
1673
1881
  @dataset.opts[:sql] = "select abc from xyz"
1674
1882
  @dataset.count.should == 1
1675
- @c.sql.should == "SELECT COUNT(*) AS count FROM (select abc from xyz) AS t1 LIMIT 1"
1883
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (select abc from xyz) AS t1 LIMIT 1"]
1676
1884
  end
1677
1885
 
1678
1886
  specify "should count properly when using UNION, INTERSECT, or EXCEPT" do
1679
1887
  @dataset.union(@dataset).count.should == 1
1680
- @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 1"
1888
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 1"]
1681
1889
  @dataset.intersect(@dataset).count.should == 1
1682
- @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test INTERSECT SELECT * FROM test) AS t1 LIMIT 1"
1890
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test INTERSECT SELECT * FROM test) AS t1 LIMIT 1"]
1683
1891
  @dataset.except(@dataset).count.should == 1
1684
- @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test EXCEPT SELECT * FROM test) AS t1 LIMIT 1"
1892
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test EXCEPT SELECT * FROM test) AS t1 LIMIT 1"]
1685
1893
  end
1686
1894
 
1687
1895
  specify "should return limit if count is greater than it" do
1688
1896
  @dataset.limit(5).count.should == 1
1689
- @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"
1897
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"]
1690
1898
  end
1691
1899
 
1692
1900
  it "should work on a graphed_dataset" do
1693
1901
  @dataset.should_receive(:columns).twice.and_return([:a])
1694
1902
  @dataset.graph(@dataset, [:a], :table_alias=>:test2).count.should == 1
1695
- @c.sql.should == 'SELECT COUNT(*) AS count FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1'
1903
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1']
1696
1904
  end
1697
1905
 
1698
1906
  specify "should not cache the columns value" do
1699
- ds = @dataset.from(:blah)
1907
+ ds = @dataset.from(:blah).columns(:a)
1700
1908
  ds.columns.should == [:a]
1701
1909
  ds.count.should == 1
1702
- @c.sql.should == 'SELECT COUNT(*) AS count FROM blah LIMIT 1'
1910
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM blah LIMIT 1']
1703
1911
  ds.columns.should == [:a]
1704
1912
  end
1705
1913
  end
1706
1914
 
1707
-
1708
1915
  describe "Dataset#group_and_count" do
1709
1916
  before do
1710
- @c = Class.new(Sequel::Dataset) do
1711
- def self.sql
1712
- @@sql
1713
- end
1714
-
1715
- def fetch_rows(sql)
1716
- @@sql = sql
1717
- yield({1 => 1})
1718
- end
1719
- end
1720
- @ds = @c.new(nil).from(:test)
1917
+ @ds = Sequel::Dataset.new(nil).from(:test)
1721
1918
  end
1722
1919
 
1723
1920
  specify "should format SQL properly" do
1724
- @ds.group_and_count(:name).sql.should ==
1725
- "SELECT name, count(*) AS count FROM test GROUP BY name"
1921
+ @ds.group_and_count(:name).sql.should == "SELECT name, count(*) AS count FROM test GROUP BY name"
1726
1922
  end
1727
1923
 
1728
1924
  specify "should accept multiple columns for grouping" do
1729
- @ds.group_and_count(:a, :b).sql.should ==
1730
- "SELECT a, b, count(*) AS count FROM test GROUP BY a, b"
1925
+ @ds.group_and_count(:a, :b).sql.should == "SELECT a, b, count(*) AS count FROM test GROUP BY a, b"
1731
1926
  end
1732
1927
 
1733
1928
  specify "should format column aliases in the select clause but not in the group clause" do
1734
- @ds.group_and_count(:name___n).sql.should ==
1735
- "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1736
- @ds.group_and_count(:name__n).sql.should ==
1737
- "SELECT name.n, count(*) AS count FROM test GROUP BY name.n"
1929
+ @ds.group_and_count(:name___n).sql.should == "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1930
+ @ds.group_and_count(:name__n).sql.should == "SELECT name.n, count(*) AS count FROM test GROUP BY name.n"
1738
1931
  end
1739
1932
 
1740
1933
  specify "should handle identifiers" do
1741
- @ds.group_and_count(:name___n.identifier).sql.should ==
1742
- "SELECT name___n, count(*) AS count FROM test GROUP BY name___n"
1934
+ @ds.group_and_count(:name___n.identifier).sql.should == "SELECT name___n, count(*) AS count FROM test GROUP BY name___n"
1743
1935
  end
1744
1936
 
1745
1937
  specify "should handle literal strings" do
1746
- @ds.group_and_count("name".lit).sql.should ==
1747
- "SELECT name, count(*) AS count FROM test GROUP BY name"
1938
+ @ds.group_and_count("name".lit).sql.should == "SELECT name, count(*) AS count FROM test GROUP BY name"
1748
1939
  end
1749
1940
 
1750
1941
  specify "should handle aliased expressions" do
1751
- @ds.group_and_count(:name.as(:n)).sql.should ==
1752
- "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1753
- @ds.group_and_count(:name.identifier.as(:n)).sql.should ==
1754
- "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1942
+ @ds.group_and_count(:name.as(:n)).sql.should == "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1943
+ @ds.group_and_count(:name.identifier.as(:n)).sql.should == "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1944
+ end
1945
+
1946
+ specify "should take a virtual row block" do
1947
+ @ds.group_and_count{(type_id > 1).as(t)}.sql.should == "SELECT (type_id > 1) AS t, count(*) AS count FROM test GROUP BY (type_id > 1)"
1948
+ @ds.group_and_count{[(type_id > 1).as(t), type_id < 2]}.sql.should == "SELECT (type_id > 1) AS t, (type_id < 2), count(*) AS count FROM test GROUP BY (type_id > 1), (type_id < 2)"
1949
+ @ds.group_and_count(:foo){type_id > 1}.sql.should == "SELECT foo, (type_id > 1), count(*) AS count FROM test GROUP BY foo, (type_id > 1)"
1755
1950
  end
1756
1951
  end
1757
1952
 
1758
1953
  describe "Dataset#empty?" do
1759
1954
  specify "should return true if records exist in the dataset" do
1760
- @c = Class.new(Sequel::Dataset) do
1761
- def self.sql
1762
- @@sql
1763
- end
1764
-
1765
- def fetch_rows(sql)
1766
- @@sql = sql
1767
- yield({1 => 1}) unless sql =~ /WHERE 'f'/
1768
- end
1769
- end
1770
- @c.new(nil).from(:test).should_not be_empty
1771
- @c.sql.should == 'SELECT 1 FROM test LIMIT 1'
1772
- @c.new(nil).from(:test).filter(false).should be_empty
1773
- @c.sql.should == "SELECT 1 FROM test WHERE 'f' LIMIT 1"
1955
+ db = Sequel.mock(:fetch=>proc{|sql| {1=>1} unless sql =~ /WHERE 'f'/})
1956
+ db.from(:test).should_not be_empty
1957
+ db.sqls.should == ['SELECT 1 AS one FROM test LIMIT 1']
1958
+ db.from(:test).filter(false).should be_empty
1959
+ db.sqls.should == ["SELECT 1 AS one FROM test WHERE 'f' LIMIT 1"]
1774
1960
  end
1775
1961
  end
1776
1962
 
@@ -1817,9 +2003,9 @@ describe "Dataset#first_source_table" do
1817
2003
  end
1818
2004
 
1819
2005
  specify "should be the unaliased part if aliased" do
1820
- @ds.from(:t___a).first_source_table.should == :t.identifier
1821
- @ds.from(:s__t___a).first_source_table.should == :t.qualify(:s)
1822
- @ds.from(:t.as(:a)).first_source_table.should == :t
2006
+ @ds.literal(@ds.from(:t___a).first_source_table).should == "t"
2007
+ @ds.literal(@ds.from(:s__t___a).first_source_table).should == "s.t"
2008
+ @ds.literal(@ds.from(:t.as(:a)).first_source_table).should == "t"
1823
2009
  end
1824
2010
 
1825
2011
  specify "should raise exception if table doesn't have a source" do
@@ -1849,25 +2035,31 @@ describe "Dataset#from_self" do
1849
2035
  'SELECT * FROM (SELECT name FROM test LIMIT 1) AS some_name INNER JOIN posts ON (posts.alias = some_name.name)'
1850
2036
  end
1851
2037
 
1852
- specify "should not options such as server" do
2038
+ specify "should not remove non-SQL options such as :server" do
1853
2039
  @ds.server(:blah).from_self(:alias=>:some_name).opts[:server].should == :blah
1854
2040
  end
1855
2041
 
2042
+ specify "should hoist WITH clauses in current dataset if dataset doesn't support WITH in subselect" do
2043
+ ds = Sequel::Dataset.new(nil)
2044
+ ds.meta_def(:supports_cte?){true}
2045
+ ds.meta_def(:supports_cte_in_subselect?){false}
2046
+ ds.from(:a).with(:a, ds.from(:b)).from_self.sql.should == 'WITH a AS (SELECT * FROM b) SELECT * FROM (SELECT * FROM a) AS t1'
2047
+ ds.from(:a, :c).with(:a, ds.from(:b)).with(:c, ds.from(:d)).from_self.sql.should == 'WITH a AS (SELECT * FROM b), c AS (SELECT * FROM d) SELECT * FROM (SELECT * FROM a, c) AS t1'
2048
+ end
1856
2049
  end
1857
2050
 
1858
2051
  describe "Dataset#join_table" do
1859
2052
  before do
1860
- @d = MockDataset.new(nil).from(:items)
2053
+ @d = Sequel::Dataset.new(nil).from(:items)
1861
2054
  @d.quote_identifiers = true
1862
2055
  end
1863
2056
 
1864
2057
  specify "should format the JOIN clause properly" do
1865
- @d.join_table(:left_outer, :categories, :category_id => :id).sql.should ==
1866
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2058
+ @d.join_table(:left_outer, :categories, :category_id => :id).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1867
2059
  end
1868
2060
 
1869
2061
  specify "should handle multiple conditions on the same join table column" do
1870
- @d.join_table(:left_outer, :categories, [[:category_id, :id], [:category_id, 0..100]]).sql.should ==
2062
+ @d.join_table(:left_outer, :categories, [[:category_id, :id], [:category_id, 0..100]]).sql.should ==
1871
2063
  'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON (("categories"."category_id" = "items"."id") AND ("categories"."category_id" >= 0) AND ("categories"."category_id" <= 100))'
1872
2064
  end
1873
2065
 
@@ -1877,45 +2069,30 @@ describe "Dataset#join_table" do
1877
2069
  end
1878
2070
 
1879
2071
  specify "should include ORDER BY clause if applicable" do
1880
- @d.order(:stamp).join_table(:full_outer, :categories, :category_id => :id).sql.should ==
1881
- 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") ORDER BY "stamp"'
2072
+ @d.order(:stamp).join_table(:full_outer, :categories, :category_id => :id).sql.should == 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") ORDER BY "stamp"'
1882
2073
  end
1883
2074
 
1884
2075
  specify "should support multiple joins" do
1885
- @d.join_table(:inner, :b, :items_id=>:id).join_table(:left_outer, :c, :b_id => :b__id).sql.should ==
1886
- 'SELECT * FROM "items" INNER JOIN "b" ON ("b"."items_id" = "items"."id") LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")'
2076
+ @d.join_table(:inner, :b, :items_id=>:id).join_table(:left_outer, :c, :b_id => :b__id).sql.should == 'SELECT * FROM "items" INNER JOIN "b" ON ("b"."items_id" = "items"."id") LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")'
1887
2077
  end
1888
2078
 
1889
2079
  specify "should support arbitrary join types" do
1890
- @d.join_table(:magic, :categories, :category_id=>:id).sql.should ==
1891
- 'SELECT * FROM "items" MAGIC JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2080
+ @d.join_table(:magic, :categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" MAGIC JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1892
2081
  end
1893
2082
 
1894
2083
  specify "should support many join methods" do
1895
- @d.left_outer_join(:categories, :category_id=>:id).sql.should ==
1896
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1897
- @d.right_outer_join(:categories, :category_id=>:id).sql.should ==
1898
- 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1899
- @d.full_outer_join(:categories, :category_id=>:id).sql.should ==
1900
- 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1901
- @d.inner_join(:categories, :category_id=>:id).sql.should ==
1902
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1903
- @d.left_join(:categories, :category_id=>:id).sql.should ==
1904
- 'SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1905
- @d.right_join(:categories, :category_id=>:id).sql.should ==
1906
- 'SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1907
- @d.full_join(:categories, :category_id=>:id).sql.should ==
1908
- 'SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1909
- @d.natural_join(:categories).sql.should ==
1910
- 'SELECT * FROM "items" NATURAL JOIN "categories"'
1911
- @d.natural_left_join(:categories).sql.should ==
1912
- 'SELECT * FROM "items" NATURAL LEFT JOIN "categories"'
1913
- @d.natural_right_join(:categories).sql.should ==
1914
- 'SELECT * FROM "items" NATURAL RIGHT JOIN "categories"'
1915
- @d.natural_full_join(:categories).sql.should ==
1916
- 'SELECT * FROM "items" NATURAL FULL JOIN "categories"'
1917
- @d.cross_join(:categories).sql.should ==
1918
- 'SELECT * FROM "items" CROSS JOIN "categories"'
2084
+ @d.left_outer_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2085
+ @d.right_outer_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2086
+ @d.full_outer_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2087
+ @d.inner_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2088
+ @d.left_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2089
+ @d.right_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2090
+ @d.full_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2091
+ @d.natural_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL JOIN "categories"'
2092
+ @d.natural_left_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL LEFT JOIN "categories"'
2093
+ @d.natural_right_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL RIGHT JOIN "categories"'
2094
+ @d.natural_full_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL FULL JOIN "categories"'
2095
+ @d.cross_join(:categories).sql.should == 'SELECT * FROM "items" CROSS JOIN "categories"'
1919
2096
  end
1920
2097
 
1921
2098
  specify "should raise an error if additional arguments are provided to join methods that don't take conditions" do
@@ -1935,95 +2112,68 @@ describe "Dataset#join_table" do
1935
2112
  end
1936
2113
 
1937
2114
  specify "should default to a plain join if nil is used for the type" do
1938
- @d.join_table(nil, :categories, :category_id=>:id).sql.should ==
1939
- 'SELECT * FROM "items" JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2115
+ @d.join_table(nil, :categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1940
2116
  end
1941
2117
 
1942
2118
  specify "should use an inner join for Dataset#join" do
1943
- @d.join(:categories, :category_id=>:id).sql.should ==
1944
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2119
+ @d.join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1945
2120
  end
1946
2121
 
1947
2122
  specify "should support aliased tables using the deprecated argument" do
1948
- @d.from('stats').join('players', {:id => :player_id}, 'p').sql.should ==
1949
- 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
2123
+ @d.from('stats').join('players', {:id => :player_id}, 'p').sql.should == 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1950
2124
  end
1951
2125
 
1952
2126
  specify "should support aliased tables using the :table_alias option" do
1953
- @d.from('stats').join('players', {:id => :player_id}, :table_alias=>:p).sql.should ==
1954
- 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
2127
+ @d.from('stats').join('players', {:id => :player_id}, :table_alias=>:p).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1955
2128
  end
1956
2129
 
1957
2130
  specify "should support aliased tables using an implicit alias" do
1958
- @d.from('stats').join(:players.as(:p), {:id => :player_id}).sql.should ==
1959
- 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
2131
+ @d.from('stats').join(:players.as(:p), {:id => :player_id}).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1960
2132
  end
1961
2133
 
1962
2134
  specify "should support using an alias for the FROM when doing the first join with unqualified condition columns" do
1963
- ds = MockDataset.new(nil).from(:foo => :f)
1964
- ds.quote_identifiers = true
1965
- ds.join_table(:inner, :bar, :id => :bar_id).sql.should ==
1966
- 'SELECT * FROM "foo" AS "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
2135
+ @d.from(:foo=>:f).join_table(:inner, :bar, :id => :bar_id).sql.should == 'SELECT * FROM "foo" AS "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
1967
2136
  end
1968
2137
 
1969
2138
  specify "should support implicit schemas in from table symbols" do
1970
- @d.from(:s__t).join(:u__v, {:id => :player_id}).sql.should ==
1971
- 'SELECT * FROM "s"."t" INNER JOIN "u"."v" ON ("u"."v"."id" = "s"."t"."player_id")'
2139
+ @d.from(:s__t).join(:u__v, {:id => :player_id}).sql.should == 'SELECT * FROM "s"."t" INNER JOIN "u"."v" ON ("u"."v"."id" = "s"."t"."player_id")'
1972
2140
  end
1973
2141
 
1974
2142
  specify "should support implicit aliases in from table symbols" do
1975
- @d.from(:t___z).join(:v___y, {:id => :player_id}).sql.should ==
1976
- 'SELECT * FROM "t" AS "z" INNER JOIN "v" AS "y" ON ("y"."id" = "z"."player_id")'
1977
- @d.from(:s__t___z).join(:u__v___y, {:id => :player_id}).sql.should ==
1978
- 'SELECT * FROM "s"."t" AS "z" INNER JOIN "u"."v" AS "y" ON ("y"."id" = "z"."player_id")'
2143
+ @d.from(:t___z).join(:v___y, {:id => :player_id}).sql.should == 'SELECT * FROM "t" AS "z" INNER JOIN "v" AS "y" ON ("y"."id" = "z"."player_id")'
2144
+ @d.from(:s__t___z).join(:u__v___y, {:id => :player_id}).sql.should == 'SELECT * FROM "s"."t" AS "z" INNER JOIN "u"."v" AS "y" ON ("y"."id" = "z"."player_id")'
1979
2145
  end
1980
2146
 
1981
2147
  specify "should support AliasedExpressions" do
1982
- @d.from(:s.as(:t)).join(:u.as(:v), {:id => :player_id}).sql.should ==
1983
- 'SELECT * FROM "s" AS "t" INNER JOIN "u" AS "v" ON ("v"."id" = "t"."player_id")'
2148
+ @d.from(:s.as(:t)).join(:u.as(:v), {:id => :player_id}).sql.should == 'SELECT * FROM "s" AS "t" INNER JOIN "u" AS "v" ON ("v"."id" = "t"."player_id")'
1984
2149
  end
1985
2150
 
1986
2151
  specify "should support the :implicit_qualifier option" do
1987
- @d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should ==
1988
- 'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
2152
+ @d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
1989
2153
  end
1990
2154
 
1991
2155
  specify "should allow for arbitrary conditions in the JOIN clause" do
1992
- @d.join_table(:left_outer, :categories, :status => 0).sql.should ==
1993
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" = 0)'
1994
- @d.join_table(:left_outer, :categories, :categorizable_type => "Post").sql.should ==
1995
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categorizable_type" = \'Post\')'
1996
- @d.join_table(:left_outer, :categories, :timestamp => "CURRENT_TIMESTAMP".lit).sql.should ==
1997
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."timestamp" = CURRENT_TIMESTAMP)'
1998
- @d.join_table(:left_outer, :categories, :status => [1, 2, 3]).sql.should ==
1999
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" IN (1, 2, 3))'
2156
+ @d.join_table(:left_outer, :categories, :status => 0).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" = 0)'
2157
+ @d.join_table(:left_outer, :categories, :categorizable_type => "Post").sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categorizable_type" = \'Post\')'
2158
+ @d.join_table(:left_outer, :categories, :timestamp => "CURRENT_TIMESTAMP".lit).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."timestamp" = CURRENT_TIMESTAMP)'
2159
+ @d.join_table(:left_outer, :categories, :status => [1, 2, 3]).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" IN (1, 2, 3))'
2000
2160
  end
2001
2161
 
2002
2162
  specify "should raise error for a table without a source" do
2003
- proc {Sequel::Dataset.new(nil).join('players', :id => :player_id)}. \
2004
- should raise_error(Sequel::Error)
2163
+ proc {Sequel::Dataset.new(nil).join('players', :id => :player_id)}.should raise_error(Sequel::Error)
2005
2164
  end
2006
2165
 
2007
2166
  specify "should support joining datasets" do
2008
2167
  ds = Sequel::Dataset.new(nil).from(:categories)
2009
-
2010
- @d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
2011
- 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id")'
2012
-
2168
+ @d.join_table(:left_outer, ds, :item_id => :id).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id")'
2013
2169
  ds.filter!(:active => true)
2014
-
2015
- @d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
2016
- 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t1" ON ("t1"."item_id" = "items"."id")'
2017
-
2018
- @d.from_self.join_table(:left_outer, ds, :item_id => :id).sql.should ==
2019
- '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")'
2170
+ @d.join_table(:left_outer, ds, :item_id => :id).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t1" ON ("t1"."item_id" = "items"."id")'
2171
+ @d.from_self.join_table(:left_outer, ds, :item_id => :id).sql.should == '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")'
2020
2172
  end
2021
2173
 
2022
2174
  specify "should support joining datasets and aliasing the join" do
2023
2175
  ds = Sequel::Dataset.new(nil).from(:categories)
2024
-
2025
- @d.join_table(:left_outer, ds, {:ds__item_id => :id}, :ds).sql.should ==
2026
- 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "ds" ON ("ds"."item_id" = "items"."id")'
2176
+ @d.join_table(:left_outer, ds, {:ds__item_id => :id}, :ds).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "ds" ON ("ds"."item_id" = "items"."id")'
2027
2177
  end
2028
2178
 
2029
2179
  specify "should support joining multiple datasets" do
@@ -2040,40 +2190,30 @@ describe "Dataset#join_table" do
2040
2190
  specify "should support joining objects that respond to :table_name" do
2041
2191
  ds = Object.new
2042
2192
  def ds.table_name; :categories end
2043
-
2044
- @d.join(ds, :item_id => :id).sql.should ==
2045
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."item_id" = "items"."id")'
2193
+ @d.join(ds, :item_id => :id).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."item_id" = "items"."id")'
2046
2194
  end
2047
2195
 
2048
- specify "should support using a SQL String as the join condition" do
2049
- @d.join(:categories, %{c.item_id = items.id}, :c).sql.should ==
2050
- 'SELECT * FROM "items" INNER JOIN "categories" AS "c" ON (c.item_id = items.id)'
2196
+ specify "should support using an SQL String as the join condition" do
2197
+ @d.join(:categories, "c.item_id = items.id", :c).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" AS "c" ON (c.item_id = items.id)'
2051
2198
  end
2052
2199
 
2053
2200
  specify "should support using a boolean column as the join condition" do
2054
- @d.join(:categories, :active).sql.should ==
2055
- 'SELECT * FROM "items" INNER JOIN "categories" ON "active"'
2201
+ @d.join(:categories, :active).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON "active"'
2056
2202
  end
2057
2203
 
2058
2204
  specify "should support using an expression as the join condition" do
2059
- @d.join(:categories, :number.sql_number > 10).sql.should ==
2060
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("number" > 10)'
2205
+ @d.join(:categories, :number.sql_number > 10).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("number" > 10)'
2061
2206
  end
2062
2207
 
2063
- specify "should support natural and cross joins using nil" do
2064
- @d.join_table(:natural, :categories).sql.should ==
2065
- 'SELECT * FROM "items" NATURAL JOIN "categories"'
2066
- @d.join_table(:cross, :categories, nil).sql.should ==
2067
- 'SELECT * FROM "items" CROSS JOIN "categories"'
2068
- @d.join_table(:natural, :categories, nil, :c).sql.should ==
2069
- 'SELECT * FROM "items" NATURAL JOIN "categories" AS "c"'
2208
+ specify "should support natural and cross joins" do
2209
+ @d.join_table(:natural, :categories).sql.should == 'SELECT * FROM "items" NATURAL JOIN "categories"'
2210
+ @d.join_table(:cross, :categories, nil).sql.should == 'SELECT * FROM "items" CROSS JOIN "categories"'
2211
+ @d.join_table(:natural, :categories, nil, :c).sql.should == 'SELECT * FROM "items" NATURAL JOIN "categories" AS "c"'
2070
2212
  end
2071
2213
 
2072
2214
  specify "should support joins with a USING clause if an array of symbols is used" do
2073
- @d.join(:categories, [:id]).sql.should ==
2074
- 'SELECT * FROM "items" INNER JOIN "categories" USING ("id")'
2075
- @d.join(:categories, [:id1, :id2]).sql.should ==
2076
- 'SELECT * FROM "items" INNER JOIN "categories" USING ("id1", "id2")'
2215
+ @d.join(:categories, [:id]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" USING ("id")'
2216
+ @d.join(:categories, [:id1, :id2]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" USING ("id1", "id2")'
2077
2217
  end
2078
2218
 
2079
2219
  specify "should emulate JOIN USING (poorly) if the dataset doesn't support it" do
@@ -2081,6 +2221,12 @@ describe "Dataset#join_table" do
2081
2221
  @d.join(:categories, [:id]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."id" = "items"."id")'
2082
2222
  end
2083
2223
 
2224
+ specify "should hoist WITH clauses from subqueries if the dataset doesn't support CTEs in subselects" do
2225
+ @d.meta_def(:supports_cte?){true}
2226
+ @d.meta_def(:supports_cte_in_subselect?){false}
2227
+ @d.join(Sequel::Dataset.new(nil).from(:categories).with(:a, Sequel::Dataset.new(nil).from(:b)), [:id]).sql.should == 'WITH "a" AS (SELECT * FROM b) SELECT * FROM "items" INNER JOIN (SELECT * FROM categories) AS "t1" USING ("id")'
2228
+ end
2229
+
2084
2230
  specify "should raise an error if using an array of symbols with a block" do
2085
2231
  proc{@d.join(:categories, [:id]){|j,lj,js|}}.should raise_error(Sequel::Error)
2086
2232
  end
@@ -2135,27 +2281,21 @@ describe "Dataset#join_table" do
2135
2281
  end
2136
2282
 
2137
2283
  specify "should use the block result as the only condition if no condition is given" do
2138
- @d.join(:categories){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
2139
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" = "items"."c")'
2140
- @d.join(:categories){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
2141
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" > "items"."c")'
2284
+ @d.join(:categories){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" = "items"."c")'
2285
+ @d.join(:categories){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" > "items"."c")'
2142
2286
  end
2143
2287
 
2144
2288
  specify "should combine the block conditions and argument conditions if both given" do
2145
- @d.join(:categories, :a=>:d){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
2146
- 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" = "items"."c"))'
2147
- @d.join(:categories, :a=>:d){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
2148
- 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" > "items"."c"))'
2289
+ @d.join(:categories, :a=>:d){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" = "items"."c"))'
2290
+ @d.join(:categories, :a=>:d){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" > "items"."c"))'
2149
2291
  end
2150
2292
 
2151
2293
  specify "should prefer explicit aliases over implicit" do
2152
- @d.from(:items___i).join(:categories___c, {:category_id => :id}, {:table_alias=>:c2, :implicit_qualifier=>:i2}).sql.should ==
2153
- 'SELECT * FROM "items" AS "i" INNER JOIN "categories" AS "c2" ON ("c2"."category_id" = "i2"."id")'
2294
+ @d.from(:items___i).join(:categories___c, {:category_id => :id}, {:table_alias=>:c2, :implicit_qualifier=>:i2}).sql.should == 'SELECT * FROM "items" AS "i" INNER JOIN "categories" AS "c2" ON ("c2"."category_id" = "i2"."id")'
2154
2295
  @d.from(:items.as(:i)).join(:categories.as(:c), {:category_id => :id}, {:table_alias=>:c2, :implicit_qualifier=>:i2}).sql.should ==
2155
2296
  'SELECT * FROM "items" AS "i" INNER JOIN "categories" AS "c2" ON ("c2"."category_id" = "i2"."id")'
2156
2297
  end
2157
2298
 
2158
-
2159
2299
  specify "should not allow insert, update, delete, or truncate" do
2160
2300
  proc{@d.join(:categories, :a=>:d).insert_sql}.should raise_error(Sequel::InvalidOperation)
2161
2301
  proc{@d.join(:categories, :a=>:d).update_sql(:a=>1)}.should raise_error(Sequel::InvalidOperation)
@@ -2170,81 +2310,52 @@ describe "Dataset#join_table" do
2170
2310
  end
2171
2311
 
2172
2312
  describe "Dataset#[]=" do
2173
- before do
2174
- c = Class.new(Sequel::Dataset) do
2175
- def last_sql
2176
- @@last_sql
2177
- end
2178
-
2179
- def update(*args)
2180
- @@last_sql = update_sql(*args)
2181
- end
2182
- end
2183
-
2184
- @d = c.new(nil).from(:items)
2185
- end
2186
-
2187
2313
  specify "should perform an update on the specified filter" do
2188
- @d[:a => 1] = {:x => 3}
2189
- @d.last_sql.should == 'UPDATE items SET x = 3 WHERE (a = 1)'
2314
+ db = Sequel.mock
2315
+ ds = db[:items]
2316
+ ds[:a => 1] = {:x => 3}
2317
+ db.sqls.should == ['UPDATE items SET x = 3 WHERE (a = 1)']
2190
2318
  end
2191
2319
  end
2192
2320
 
2193
2321
  describe "Dataset#set" do
2194
- before do
2195
- c = Class.new(Sequel::Dataset) do
2196
- def last_sql
2197
- @@last_sql
2198
- end
2199
-
2200
- def update(*args, &block)
2201
- @@last_sql = update_sql(*args, &block)
2202
- end
2203
- end
2204
-
2205
- @d = c.new(nil).from(:items)
2206
- end
2207
-
2208
2322
  specify "should act as alias to #update" do
2209
- @d.set({:x => 3})
2210
- @d.last_sql.should == 'UPDATE items SET x = 3'
2323
+ db = Sequel.mock
2324
+ ds = db[:items]
2325
+ ds.set({:x => 3})
2326
+ db.sqls.should == ['UPDATE items SET x = 3']
2211
2327
  end
2212
2328
  end
2213
2329
 
2214
-
2215
2330
  describe "Dataset#insert_multiple" do
2216
2331
  before do
2217
- c = Class.new(Sequel::Dataset) do
2218
- attr_reader :inserts
2219
- def insert(arg)
2220
- @inserts ||= []
2221
- @inserts << arg
2222
- end
2223
- end
2224
-
2225
- @d = c.new(nil)
2332
+ @db = Sequel.mock(:autoid=>2)
2333
+ @ds = @db[:items]
2226
2334
  end
2227
2335
 
2228
2336
  specify "should insert all items in the supplied array" do
2229
- @d.insert_multiple [:aa, 5, 3, {1 => 2}]
2230
- @d.inserts.should == [:aa, 5, 3, {1 => 2}]
2337
+ @ds.insert_multiple(['aa', 5, 3, {:a => 2}])
2338
+ @db.sqls.should == ["INSERT INTO items VALUES ('aa')",
2339
+ "INSERT INTO items VALUES (5)",
2340
+ "INSERT INTO items VALUES (3)",
2341
+ "INSERT INTO items (a) VALUES (2)"]
2231
2342
  end
2232
2343
 
2233
2344
  specify "should pass array items through the supplied block if given" do
2234
- a = ["inevitable", "hello", "the ticking clock"]
2235
- @d.insert_multiple(a) {|i| i.gsub('l', 'r')}
2236
- @d.inserts.should == ["inevitabre", "herro", "the ticking crock"]
2345
+ @ds.insert_multiple(["inevitable", "hello", "the ticking clock"]){|i| i.gsub('l', 'r')}
2346
+ @db.sqls.should == ["INSERT INTO items VALUES ('inevitabre')",
2347
+ "INSERT INTO items VALUES ('herro')",
2348
+ "INSERT INTO items VALUES ('the ticking crock')"]
2349
+ end
2350
+
2351
+ specify "should return array of inserted ids" do
2352
+ @ds.insert_multiple(['aa', 5, 3, {:a => 2}]).should == [2, 3, 4, 5]
2237
2353
  end
2238
2354
  end
2239
2355
 
2240
2356
  describe "Dataset aggregate methods" do
2241
2357
  before do
2242
- c = Class.new(Sequel::Dataset) do
2243
- def fetch_rows(sql)
2244
- yield({1 => sql})
2245
- end
2246
- end
2247
- @d = c.new(nil).from(:test)
2358
+ @d = Sequel.mock(:fetch=>proc{|s| {1=>s}})[:test]
2248
2359
  end
2249
2360
 
2250
2361
  specify "should include min" do
@@ -2278,116 +2389,88 @@ end
2278
2389
 
2279
2390
  describe "Dataset#range" do
2280
2391
  before do
2281
- c = Class.new(Sequel::Dataset) do
2282
- class_variable_set(:@@sql, nil)
2283
-
2284
- def last_sql; self.class.send(:class_variable_get, :@@sql); end
2285
-
2286
- def fetch_rows(sql)
2287
- self.class.send(:class_variable_set, :@@sql, sql)
2288
- yield(:v1 => 1, :v2 => 10)
2289
- end
2290
- end
2291
- @d = c.new(nil).from(:test)
2392
+ @db = Sequel.mock(:fetch=>{:v1 => 1, :v2 => 10})
2393
+ @ds = @db[:test]
2292
2394
  end
2293
2395
 
2294
2396
  specify "should generate a correct SQL statement" do
2295
- @d.range(:stamp)
2296
- @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test LIMIT 1"
2397
+ @ds.range(:stamp)
2398
+ @db.sqls.should == ["SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test LIMIT 1"]
2297
2399
 
2298
- @d.filter(:price.sql_number > 100).range(:stamp)
2299
- @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test WHERE (price > 100) LIMIT 1"
2400
+ @ds.filter(:price.sql_number > 100).range(:stamp)
2401
+ @db.sqls.should == ["SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test WHERE (price > 100) LIMIT 1"]
2300
2402
  end
2301
2403
 
2302
2404
  specify "should return a range object" do
2303
- @d.range(:tryme).should == (1..10)
2405
+ @ds.range(:tryme).should == (1..10)
2304
2406
  end
2305
2407
 
2306
2408
  specify "should use a subselect for the same conditions as count" do
2307
- @d.order(:stamp).limit(5).range(:stamp).should == (1..10)
2308
- @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'
2409
+ @ds.order(:stamp).limit(5).range(:stamp).should == (1..10)
2410
+ @db.sqls.should == ['SELECT min(stamp) AS v1, max(stamp) AS v2 FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1']
2309
2411
  end
2310
2412
  end
2311
2413
 
2312
2414
  describe "Dataset#interval" do
2313
2415
  before do
2314
- c = Class.new(Sequel::Dataset) do
2315
- class_variable_set(:@@sql, nil)
2316
-
2317
- def last_sql; self.class.send(:class_variable_get, :@@sql); end
2318
-
2319
- def fetch_rows(sql)
2320
- self.class.send(:class_variable_set, :@@sql, sql)
2321
- yield(:v => 1234)
2322
- end
2323
- end
2324
- @d = c.new(nil).from(:test)
2416
+ @db = Sequel.mock(:fetch=>{:v => 1234})
2417
+ @ds = @db[:test]
2325
2418
  end
2326
2419
 
2327
- specify "should generate a correct SQL statement" do
2328
- @d.interval(:stamp)
2329
- @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test LIMIT 1"
2420
+ specify "should generate the correct SQL statement" do
2421
+ @ds.interval(:stamp)
2422
+ @db.sqls.should == ["SELECT (max(stamp) - min(stamp)) FROM test LIMIT 1"]
2330
2423
 
2331
- @d.filter(:price.sql_number > 100).interval(:stamp)
2332
- @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test WHERE (price > 100) LIMIT 1"
2333
- end
2334
-
2335
- specify "should return an integer" do
2336
- @d.interval(:tryme).should == 1234
2424
+ @ds.filter(:price.sql_number > 100).interval(:stamp)
2425
+ @db.sqls.should == ["SELECT (max(stamp) - min(stamp)) FROM test WHERE (price > 100) LIMIT 1"]
2337
2426
  end
2338
2427
 
2339
2428
  specify "should use a subselect for the same conditions as count" do
2340
- @d.order(:stamp).limit(5).interval(:stamp).should == 1234
2341
- @d.last_sql.should == 'SELECT (max(stamp) - min(stamp)) FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1'
2429
+ @ds.order(:stamp).limit(5).interval(:stamp).should == 1234
2430
+ @db.sqls.should == ['SELECT (max(stamp) - min(stamp)) FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1']
2342
2431
  end
2343
2432
  end
2344
2433
 
2345
2434
  describe "Dataset #first and #last" do
2346
2435
  before do
2347
- @c = Class.new(Sequel::Dataset) do
2348
- def each(&block)
2349
- s = select_sql
2350
- x = [:a,1,:b,2,s]
2351
- i = /LIMIT (\d+)/.match(s)[1].to_i.times{yield x}
2352
- end
2353
- end
2354
- @d = @c.new(nil).from(:test)
2436
+ @db = Sequel.mock(:fetch=>proc{|s| {:s=>s}})
2437
+ @d = @db[:test]
2355
2438
  end
2356
2439
 
2357
2440
  specify "should return a single record if no argument is given" do
2358
- @d.order(:a).first.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a LIMIT 1']
2359
- @d.order(:a).last.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a DESC LIMIT 1']
2441
+ @d.order(:a).first.should == {:s=>'SELECT * FROM test ORDER BY a LIMIT 1'}
2442
+ @d.order(:a).last.should == {:s=>'SELECT * FROM test ORDER BY a DESC LIMIT 1'}
2360
2443
  end
2361
2444
 
2362
2445
  specify "should return the first/last matching record if argument is not an Integer" do
2363
- @d.order(:a).first(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a LIMIT 1']
2364
- @d.order(:a).first('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a LIMIT 1']
2365
- @d.order(:a).last(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a DESC LIMIT 1']
2366
- @d.order(:a).last('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a DESC LIMIT 1']
2446
+ @d.order(:a).first(:z => 26).should == {:s=>'SELECT * FROM test WHERE (z = 26) ORDER BY a LIMIT 1'}
2447
+ @d.order(:a).first('z = ?', 15).should == {:s=>'SELECT * FROM test WHERE (z = 15) ORDER BY a LIMIT 1'}
2448
+ @d.order(:a).last(:z => 26).should == {:s=>'SELECT * FROM test WHERE (z = 26) ORDER BY a DESC LIMIT 1'}
2449
+ @d.order(:a).last('z = ?', 15).should == {:s=>'SELECT * FROM test WHERE (z = 15) ORDER BY a DESC LIMIT 1'}
2367
2450
  end
2368
2451
 
2369
2452
  specify "should set the limit and return an array of records if the given number is > 1" do
2370
2453
  i = rand(10) + 10
2371
- r = @d.order(:a).first(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a LIMIT #{i}"]] * i
2454
+ r = @d.order(:a).first(i).should == [{:s=>"SELECT * FROM test ORDER BY a LIMIT #{i}"}]
2372
2455
  i = rand(10) + 10
2373
- r = @d.order(:a).last(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a DESC LIMIT #{i}"]] * i
2456
+ r = @d.order(:a).last(i).should == [{:s=>"SELECT * FROM test ORDER BY a DESC LIMIT #{i}"}]
2374
2457
  end
2375
2458
 
2376
2459
  specify "should return the first matching record if a block is given without an argument" do
2377
- @d.first{:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z > 26) LIMIT 1']
2378
- @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']
2460
+ @d.first{:z.sql_number > 26}.should == {:s=>'SELECT * FROM test WHERE (z > 26) LIMIT 1'}
2461
+ @d.order(:name).last{:z.sql_number > 26}.should == {:s=>'SELECT * FROM test WHERE (z > 26) ORDER BY name DESC LIMIT 1'}
2379
2462
  end
2380
2463
 
2381
2464
  specify "should combine block and standard argument filters if argument is not an Integer" do
2382
- @d.first(:y=>25){:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE ((z > 26) AND (y = 25)) LIMIT 1']
2383
- @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']
2465
+ @d.first(:y=>25){:z.sql_number > 26}.should == {:s=>'SELECT * FROM test WHERE ((z > 26) AND (y = 25)) LIMIT 1'}
2466
+ @d.order(:name).last('y = ?', 16){:z.sql_number > 26}.should == {:s=>'SELECT * FROM test WHERE ((z > 26) AND (y = 16)) ORDER BY name DESC LIMIT 1'}
2384
2467
  end
2385
2468
 
2386
2469
  specify "should filter and return an array of records if an Integer argument is provided and a block is given" do
2387
2470
  i = rand(10) + 10
2388
- 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
2471
+ r = @d.order(:a).first(i){:z.sql_number > 26}.should == [{:s=>"SELECT * FROM test WHERE (z > 26) ORDER BY a LIMIT #{i}"}]
2389
2472
  i = rand(10) + 10
2390
- 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
2473
+ r = @d.order(:a).last(i){:z.sql_number > 26}.should == [{:s=>"SELECT * FROM test WHERE (z > 26) ORDER BY a DESC LIMIT #{i}"}]
2391
2474
  end
2392
2475
 
2393
2476
  specify "#last should raise if no order is given" do
@@ -2398,10 +2481,10 @@ describe "Dataset #first and #last" do
2398
2481
  end
2399
2482
 
2400
2483
  specify "#last should invert the order" do
2401
- @d.order(:a).last.pop.should == 'SELECT * FROM test ORDER BY a DESC LIMIT 1'
2402
- @d.order(:b.desc).last.pop.should == 'SELECT * FROM test ORDER BY b ASC LIMIT 1'
2403
- @d.order(:c, :d).last.pop.should == 'SELECT * FROM test ORDER BY c DESC, d DESC LIMIT 1'
2404
- @d.order(:e.desc, :f).last.pop.should == 'SELECT * FROM test ORDER BY e ASC, f DESC LIMIT 1'
2484
+ @d.order(:a).last.should == {:s=>'SELECT * FROM test ORDER BY a DESC LIMIT 1'}
2485
+ @d.order(:b.desc).last.should == {:s=>'SELECT * FROM test ORDER BY b ASC LIMIT 1'}
2486
+ @d.order(:c, :d).last.should == {:s=>'SELECT * FROM test ORDER BY c DESC, d DESC LIMIT 1'}
2487
+ @d.order(:e.desc, :f).last.should == {:s=>'SELECT * FROM test ORDER BY e ASC, f DESC LIMIT 1'}
2405
2488
  end
2406
2489
  end
2407
2490
 
@@ -2412,55 +2495,37 @@ describe "Dataset compound operations" do
2412
2495
  end
2413
2496
 
2414
2497
  specify "should support UNION and UNION ALL" do
2415
- @a.union(@b).sql.should == \
2416
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1"
2417
- @b.union(@a, true).sql.should == \
2418
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2419
- @b.union(@a, :all=>true).sql.should == \
2420
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2498
+ @a.union(@b).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1"
2499
+ @b.union(@a, true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2500
+ @b.union(@a, :all=>true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2421
2501
  end
2422
2502
 
2423
2503
  specify "should support INTERSECT and INTERSECT ALL" do
2424
- @a.intersect(@b).sql.should == \
2425
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS t1"
2426
- @b.intersect(@a, true).sql.should == \
2427
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2428
- @b.intersect(@a, :all=>true).sql.should == \
2429
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2504
+ @a.intersect(@b).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS t1"
2505
+ @b.intersect(@a, true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2506
+ @b.intersect(@a, :all=>true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2430
2507
  end
2431
2508
 
2432
2509
  specify "should support EXCEPT and EXCEPT ALL" do
2433
- @a.except(@b).sql.should == \
2434
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1"
2435
- @b.except(@a, true).sql.should == \
2436
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2437
- @b.except(@a, :all=>true).sql.should == \
2438
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2510
+ @a.except(@b).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1"
2511
+ @b.except(@a, true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2512
+ @b.except(@a, :all=>true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2439
2513
  end
2440
2514
 
2441
2515
  specify "should support :alias option for specifying identifier" do
2442
- @a.union(@b, :alias=>:xx).sql.should == \
2443
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS xx"
2444
- @a.intersect(@b, :alias=>:xx).sql.should == \
2445
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS xx"
2446
- @a.except(@b, :alias=>:xx).sql.should == \
2447
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS xx"
2516
+ @a.union(@b, :alias=>:xx).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS xx"
2517
+ @a.intersect(@b, :alias=>:xx).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS xx"
2518
+ @a.except(@b, :alias=>:xx).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS xx"
2448
2519
  end
2449
2520
 
2450
2521
  specify "should support :from_self=>false option to not wrap the compound in a SELECT * FROM (...)" do
2451
- @b.union(@a, :from_self=>false).sql.should == \
2452
- "SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1)"
2453
- @b.intersect(@a, :from_self=>false).sql.should == \
2454
- "SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)"
2455
- @b.except(@a, :from_self=>false).sql.should == \
2456
- "SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)"
2522
+ @b.union(@a, :from_self=>false).sql.should == "SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1)"
2523
+ @b.intersect(@a, :from_self=>false).sql.should == "SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)"
2524
+ @b.except(@a, :from_self=>false).sql.should == "SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)"
2457
2525
 
2458
- @b.union(@a, :from_self=>false, :all=>true).sql.should == \
2459
- "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
2460
- @b.intersect(@a, :from_self=>false, :all=>true).sql.should == \
2461
- "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
2462
- @b.except(@a, :from_self=>false, :all=>true).sql.should == \
2463
- "SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
2526
+ @b.union(@a, :from_self=>false, :all=>true).sql.should == "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
2527
+ @b.intersect(@a, :from_self=>false, :all=>true).sql.should == "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
2528
+ @b.except(@a, :from_self=>false, :all=>true).sql.should == "SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
2464
2529
  end
2465
2530
 
2466
2531
  specify "should raise an InvalidOperation if INTERSECT or EXCEPT is used and they are not supported" do
@@ -2480,141 +2545,105 @@ describe "Dataset compound operations" do
2480
2545
  end
2481
2546
 
2482
2547
  specify "should handle chained compound operations" do
2483
- @a.union(@b).union(@a, true).sql.should == \
2484
- "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"
2485
- @a.intersect(@b, true).intersect(@a).sql.should == \
2486
- "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"
2487
- @a.except(@b).except(@a, true).sql.should == \
2488
- "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"
2548
+ @a.union(@b).union(@a, true).sql.should == "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"
2549
+ @a.intersect(@b, true).intersect(@a).sql.should == "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"
2550
+ @a.except(@b).except(@a, true).sql.should == "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"
2489
2551
  end
2490
2552
 
2491
2553
  specify "should use a subselect when using a compound operation with a dataset that already has a compound operation" do
2492
- @a.union(@b.union(@a, true)).sql.should == \
2493
- "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"
2494
- @a.intersect(@b.intersect(@a), true).sql.should == \
2495
- "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"
2496
- @a.except(@b.except(@a, true)).sql.should == \
2497
- "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"
2554
+ @a.union(@b.union(@a, true)).sql.should == "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"
2555
+ @a.intersect(@b.intersect(@a), true).sql.should == "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"
2556
+ @a.except(@b.except(@a, true)).sql.should == "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"
2498
2557
  end
2499
2558
 
2500
2559
  specify "should order and limit properly when using UNION, INTERSECT, or EXCEPT" do
2501
2560
  @dataset = Sequel::Dataset.new(nil).from(:test)
2502
- @dataset.union(@dataset).limit(2).sql.should ==
2503
- "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 2"
2504
- @dataset.limit(2).intersect(@dataset).sql.should ==
2505
- "SELECT * FROM (SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1 INTERSECT SELECT * FROM test) AS t1"
2506
- @dataset.except(@dataset.limit(2)).sql.should ==
2507
- "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1) AS t1"
2508
-
2509
- @dataset.union(@dataset).order(:num).sql.should ==
2510
- "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 ORDER BY num"
2511
- @dataset.order(:num).intersect(@dataset).sql.should ==
2512
- "SELECT * FROM (SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1 INTERSECT SELECT * FROM test) AS t1"
2513
- @dataset.except(@dataset.order(:num)).sql.should ==
2514
- "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1) AS t1"
2561
+ @dataset.union(@dataset).limit(2).sql.should == "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 2"
2562
+ @dataset.limit(2).intersect(@dataset).sql.should == "SELECT * FROM (SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1 INTERSECT SELECT * FROM test) AS t1"
2563
+ @dataset.except(@dataset.limit(2)).sql.should == "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1) AS t1"
2564
+
2565
+ @dataset.union(@dataset).order(:num).sql.should == "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 ORDER BY num"
2566
+ @dataset.order(:num).intersect(@dataset).sql.should == "SELECT * FROM (SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1 INTERSECT SELECT * FROM test) AS t1"
2567
+ @dataset.except(@dataset.order(:num)).sql.should == "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1) AS t1"
2515
2568
 
2516
2569
  @dataset.limit(2).order(:a).union(@dataset.limit(3).order(:b)).order(:c).limit(4).sql.should ==
2517
2570
  "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"
2518
2571
  end
2519
2572
 
2573
+ specify "should hoist WITH clauses in given dataset if dataset doesn't support WITH in subselect" do
2574
+ ds = Sequel::Dataset.new(nil)
2575
+ ds.meta_def(:supports_cte?){true}
2576
+ ds.meta_def(:supports_cte_in_subselect?){false}
2577
+ ds.from(:a).union(ds.from(:c).with(:c, ds.from(:d)), :from_self=>false).sql.should == 'WITH c AS (SELECT * FROM d) SELECT * FROM a UNION SELECT * FROM c'
2578
+ ds.from(:a).except(ds.from(:c).with(:c, ds.from(:d))).sql.should == 'WITH c AS (SELECT * FROM d) SELECT * FROM (SELECT * FROM a EXCEPT SELECT * FROM c) AS t1'
2579
+ ds.from(:a).with(:a, ds.from(:b)).intersect(ds.from(:c).with(:c, ds.from(:d)), :from_self=>false).sql.should == 'WITH a AS (SELECT * FROM b), c AS (SELECT * FROM d) SELECT * FROM a INTERSECT SELECT * FROM c'
2580
+ end
2520
2581
  end
2521
2582
 
2522
2583
  describe "Dataset#[]" do
2523
2584
  before do
2524
- @c = Class.new(Sequel::Dataset) do
2525
- class_variable_set(:@@last_dataset, nil)
2526
-
2527
- def self.last_dataset
2528
- class_variable_get(:@@last_dataset)
2529
- end
2530
-
2531
- def single_record
2532
- self.class.send(:class_variable_set, :@@last_dataset, opts ? clone(opts) : self)
2533
- {1 => 2, 3 => 4}
2534
- end
2535
- end
2536
- @d = @c.new(nil).from(:test)
2585
+ @db = Sequel.mock(:fetch=>{1 => 2, 3 => 4})
2586
+ @d = @db[:items]
2537
2587
  end
2538
2588
 
2539
2589
  specify "should return a single record filtered according to the given conditions" do
2540
2590
  @d[:name => 'didi'].should == {1 => 2, 3 => 4}
2541
- @c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "(name = 'didi')"
2591
+ @db.sqls.should == ["SELECT * FROM items WHERE (name = 'didi') LIMIT 1"]
2542
2592
 
2543
2593
  @d[:id => 5..45].should == {1 => 2, 3 => 4}
2544
- @c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "((id >= 5) AND (id <= 45))"
2594
+ @db.sqls.should == ["SELECT * FROM items WHERE ((id >= 5) AND (id <= 45)) LIMIT 1"]
2545
2595
  end
2546
2596
  end
2547
2597
 
2548
2598
  describe "Dataset#single_record" do
2549
2599
  before do
2550
- @c = Class.new(Sequel::Dataset) do
2551
- def fetch_rows(sql)
2552
- yield sql
2553
- end
2554
- end
2555
- @cc = Class.new(@c) do
2556
- def fetch_rows(sql); end
2557
- end
2558
-
2559
- @d = @c.new(nil).from(:test)
2560
- @e = @cc.new(nil).from(:test)
2600
+ @db = Sequel.mock
2561
2601
  end
2562
2602
 
2563
2603
  specify "should call each with a limit of 1 and return the record" do
2564
- @d.single_record.should == 'SELECT * FROM test LIMIT 1'
2604
+ @db.fetch = {:a=>1}
2605
+ @db[:test].single_record.should == {:a=>1}
2606
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2565
2607
  end
2566
2608
 
2567
2609
  specify "should return nil if no record is present" do
2568
- @e.single_record.should be_nil
2610
+ @db[:test].single_record.should be_nil
2611
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2569
2612
  end
2570
2613
  end
2571
2614
 
2572
2615
  describe "Dataset#single_value" do
2573
2616
  before do
2574
- @c = Class.new(Sequel::Dataset) do
2575
- def fetch_rows(sql)
2576
- yield({1 => sql})
2577
- end
2578
- end
2579
- @cc = Class.new(@c) do
2580
- def fetch_rows(sql); end
2581
- end
2582
-
2583
- @d = @c.new(nil).from(:test)
2584
- @e = @cc.new(nil).from(:test)
2617
+ @db = Sequel.mock
2585
2618
  end
2586
2619
 
2587
2620
  specify "should call each and return the first value of the first record" do
2588
- @d.single_value.should == 'SELECT * FROM test LIMIT 1'
2621
+ @db.fetch = {:a=>1}
2622
+ @db[:test].single_value.should == 1
2623
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2589
2624
  end
2590
2625
 
2591
2626
  specify "should return nil if no records" do
2592
- @e.single_value.should be_nil
2627
+ @db[:test].single_value.should be_nil
2628
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2593
2629
  end
2594
2630
 
2595
2631
  it "should work on a graphed_dataset" do
2596
- @d.should_receive(:columns).twice.and_return([:a])
2597
- @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'
2632
+ @db.fetch = {:a=>1}
2633
+ ds = @db[:test].columns(:a)
2634
+ ds.graph(ds, [:a], :table_alias=>:test2).single_value.should == 1
2635
+ @db.sqls.should == ['SELECT test.a, test2.a AS test2_a FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1']
2598
2636
  end
2599
2637
  end
2600
2638
 
2601
2639
  describe "Dataset#get" do
2602
2640
  before do
2603
- @c = Class.new(Sequel::Dataset) do
2604
- attr_reader :last_sql
2605
-
2606
- def fetch_rows(sql)
2607
- @last_sql = sql
2608
- yield(:name => sql)
2609
- end
2610
- end
2611
-
2612
- @d = @c.new(nil).from(:test)
2641
+ @d = Sequel.mock(:fetch=>proc{|s| {:name=>s}})[:test]
2613
2642
  end
2614
2643
 
2615
2644
  specify "should select the specified column and fetch its value" do
2616
2645
  @d.get(:name).should == "SELECT name FROM test LIMIT 1"
2617
- @d.get(:abc).should == "SELECT abc FROM test LIMIT 1" # the first available value is returned always
2646
+ @d.get(:abc).should == "SELECT abc FROM test LIMIT 1"
2618
2647
  end
2619
2648
 
2620
2649
  specify "should work with filters" do
@@ -2633,243 +2662,253 @@ describe "Dataset#get" do
2633
2662
  specify "should raise an error if both a regular argument and block argument are used" do
2634
2663
  proc{@d.get(:name){|o| o.x__b.as(:name)}}.should raise_error(Sequel::Error)
2635
2664
  end
2665
+
2666
+ specify "should support false and nil values" do
2667
+ @d.get(false).should == "SELECT 'f' FROM test LIMIT 1"
2668
+ @d.get(nil).should == "SELECT NULL FROM test LIMIT 1"
2669
+ end
2636
2670
  end
2637
2671
 
2638
2672
  describe "Dataset#set_row_proc" do
2639
2673
  before do
2640
- @c = Class.new(Sequel::Dataset) do
2641
- def fetch_rows(sql, &block)
2642
- # yield a hash with kind as the 1 bit of a number
2643
- (1..10).each {|i| block.call({:kind => i[0]})}
2644
- end
2645
- end
2646
- @dataset = @c.new(nil).from(:items)
2674
+ @db = Sequel.mock(:fetch=>[{:a=>1}, {:a=>2}])
2675
+ @dataset = @db[:items]
2676
+ @dataset.row_proc = proc{|h| h[:der] = h[:a] + 2; h}
2647
2677
  end
2648
2678
 
2649
2679
  specify "should cause dataset to pass all rows through the filter" do
2650
- @dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
2651
-
2652
2680
  rows = @dataset.all
2653
- rows.size.should == 10
2654
-
2655
- rows.each {|r| r[:der].should == (r[:kind] + 2)}
2681
+ rows.map{|h| h[:der]}.should == [3, 4]
2682
+ @db.sqls.should == ['SELECT * FROM items']
2656
2683
  end
2657
2684
 
2658
2685
  specify "should be copied over when dataset is cloned" do
2659
- @dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
2660
-
2661
- @dataset.filter(:a => 1).first.should == {:kind => 1, :der => 3}
2686
+ @dataset.filter(:a => 1).all.should == [{:a=>1, :der=>3}, {:a=>2, :der=>4}]
2662
2687
  end
2663
2688
  end
2664
2689
 
2665
2690
  describe "Dataset#<<" do
2666
2691
  before do
2667
- @d = Sequel::Dataset.new(nil)
2668
- @d.meta_def(:insert) do |*args|
2669
- 1234567890
2670
- end
2692
+ @db = Sequel.mock
2671
2693
  end
2672
-
2694
+
2673
2695
  specify "should call #insert" do
2674
- (@d << {:name => 1}).should == 1234567890
2696
+ @db[:items] << {:name => 1}
2697
+ @db.sqls.should == ['INSERT INTO items (name) VALUES (1)']
2698
+ end
2699
+
2700
+ specify "should be chainable" do
2701
+ @db[:items] << {:name => 1} << @db[:old_items].select(:name)
2702
+ @db.sqls.should == ['INSERT INTO items (name) VALUES (1)', 'INSERT INTO items SELECT name FROM old_items']
2675
2703
  end
2676
2704
  end
2677
2705
 
2678
2706
  describe "Dataset#columns" do
2679
2707
  before do
2680
- @dataset = DummyDataset.new(nil).from(:items)
2681
- @dataset.meta_def(:columns=) {|c| @columns = c}
2682
- i = 'a'
2683
- @dataset.meta_def(:each){@columns = select_sql + i; i = i.next}
2708
+ @dataset = Sequel.mock[:items]
2684
2709
  end
2685
2710
 
2686
2711
  specify "should return the value of @columns if @columns is not nil" do
2687
- @dataset.columns = [:a, :b, :c]
2688
- @dataset.columns.should == [:a, :b, :c]
2712
+ @dataset.columns(:a, :b, :c).columns.should == [:a, :b, :c]
2713
+ @dataset.db.sqls.should == []
2689
2714
  end
2690
2715
 
2691
2716
  specify "should attempt to get a single record and return @columns if @columns is nil" do
2692
- @dataset.columns = nil
2693
- @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2694
- @dataset.opts[:from] = [:nana]
2695
- @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2717
+ @dataset.db.columns = [:a]
2718
+ @dataset.columns.should == [:a]
2719
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2720
+ end
2721
+
2722
+ specify "should be cleared if you change the selected columns" do
2723
+ @dataset.db.columns = [[:a], [:b]]
2724
+ @dataset.columns.should == [:a]
2725
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2726
+ @dataset.columns.should == [:a]
2727
+ @dataset.db.sqls.should == []
2728
+ ds = @dataset.select{foo{}}
2729
+ ds.columns.should == [:b]
2730
+ @dataset.db.sqls.should == ['SELECT foo() FROM items LIMIT 1']
2731
+ end
2732
+
2733
+ specify "should be cleared if you change the FROM table" do
2734
+ @dataset.db.columns = [[:a], [:b]]
2735
+ @dataset.columns.should == [:a]
2736
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2737
+ ds = @dataset.from(:foo)
2738
+ ds.columns.should == [:b]
2739
+ @dataset.db.sqls.should == ['SELECT * FROM foo LIMIT 1']
2740
+ end
2741
+
2742
+ specify "should be cleared if you join a table to the dataset" do
2743
+ @dataset.db.columns = [[:a], [:a, :b]]
2744
+ @dataset.columns.should == [:a]
2745
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2746
+ ds = @dataset.cross_join(:foo)
2747
+ ds.columns.should == [:a, :b]
2748
+ @dataset.db.sqls.should == ['SELECT * FROM items CROSS JOIN foo LIMIT 1']
2749
+ end
2750
+
2751
+ specify "should be cleared if you set custom SQL for the dataset" do
2752
+ @dataset.db.columns = [[:a], [:b]]
2753
+ @dataset.columns.should == [:a]
2754
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2755
+ ds = @dataset.with_sql('SELECT b FROM foo')
2756
+ ds.columns.should == [:b]
2757
+ @dataset.db.sqls.should == ['SELECT b FROM foo']
2696
2758
  end
2697
2759
 
2698
2760
  specify "should ignore any filters, orders, or DISTINCT clauses" do
2761
+ @dataset.db.columns = [:a]
2699
2762
  @dataset.filter!(:b=>100).order!(:b).distinct!
2700
- @dataset.columns = nil
2701
- @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2763
+ @dataset.columns.should == [:a]
2764
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2702
2765
  end
2703
2766
  end
2704
2767
 
2705
2768
  describe "Dataset#columns!" do
2706
- before do
2707
- @dataset = DummyDataset.new(nil).from(:items)
2708
- i = 'a'
2709
- @dataset.meta_def(:each){@columns = select_sql + i; i = i.next}
2710
- end
2711
-
2712
2769
  specify "should always attempt to get a record and return @columns" do
2713
- @dataset.columns!.should == 'SELECT * FROM items LIMIT 1a'
2714
- @dataset.columns!.should == 'SELECT * FROM items LIMIT 1b'
2715
- @dataset.opts[:from] = [:nana]
2716
- @dataset.columns!.should == 'SELECT * FROM nana LIMIT 1c'
2770
+ ds = Sequel.mock(:columns=>[[:a, :b, :c], [:d, :e, :f]])[:items]
2771
+ ds.columns!.should == [:a, :b, :c]
2772
+ ds.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2773
+ ds.columns!.should == [:d, :e, :f]
2774
+ ds.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2717
2775
  end
2718
2776
  end
2719
2777
 
2720
2778
  describe "Dataset#import" do
2721
2779
  before do
2722
- @dbc = Class.new(Sequel::Database) do
2723
- attr_reader :sqls
2724
-
2725
- def execute(sql, opts={})
2726
- @sqls ||= []
2727
- @sqls << sql
2728
- end
2729
- alias execute_dui execute
2730
-
2731
- def transaction(opts={})
2732
- @sqls ||= []
2733
- @sqls << 'BEGIN'
2734
- yield
2735
- @sqls << 'COMMIT'
2736
- end
2737
- end
2738
- @db = @dbc.new
2739
-
2740
- @ds = Sequel::Dataset.new(@db).from(:items)
2741
-
2742
- @list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
2780
+ @db = Sequel.mock
2781
+ @ds = @db[:items]
2743
2782
  end
2744
2783
 
2784
+ specify "should return nil without a query if no values" do
2785
+ @ds.import(['x', 'y'], []).should == nil
2786
+ @db.sqls.should == []
2787
+ end
2788
+
2745
2789
  specify "should accept string keys as column names" do
2746
2790
  @ds.import(['x', 'y'], [[1, 2], [3, 4]])
2747
- @db.sqls.should == [
2748
- 'BEGIN',
2791
+ @db.sqls.should == ['BEGIN',
2749
2792
  "INSERT INTO items (x, y) VALUES (1, 2)",
2750
2793
  "INSERT INTO items (x, y) VALUES (3, 4)",
2751
- 'COMMIT'
2752
- ]
2794
+ 'COMMIT']
2753
2795
  end
2754
2796
 
2755
2797
  specify "should accept a columns array and a values array" do
2756
2798
  @ds.import([:x, :y], [[1, 2], [3, 4]])
2757
- @db.sqls.should == [
2758
- 'BEGIN',
2799
+ @db.sqls.should == ['BEGIN',
2759
2800
  "INSERT INTO items (x, y) VALUES (1, 2)",
2760
2801
  "INSERT INTO items (x, y) VALUES (3, 4)",
2761
- 'COMMIT'
2762
- ]
2802
+ 'COMMIT']
2763
2803
  end
2764
2804
 
2765
2805
  specify "should accept a columns array and a dataset" do
2766
- @ds2 = Sequel::Dataset.new(@db).from(:cats).filter(:purr => true).select(:a, :b)
2806
+ @ds2 = @ds.from(:cats).filter(:purr => true).select(:a, :b)
2767
2807
 
2768
2808
  @ds.import([:x, :y], @ds2)
2769
- @db.sqls.should == [
2770
- 'BEGIN',
2809
+ @db.sqls.should == ['BEGIN',
2771
2810
  "INSERT INTO items (x, y) SELECT a, b FROM cats WHERE (purr IS TRUE)",
2772
- 'COMMIT'
2773
- ]
2811
+ 'COMMIT']
2774
2812
  end
2775
2813
 
2776
2814
  specify "should accept a columns array and a values array with :commit_every option" do
2777
2815
  @ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :commit_every => 3)
2778
- @db.sqls.should == [
2779
- 'BEGIN',
2816
+ @db.sqls.should == ['BEGIN',
2780
2817
  "INSERT INTO items (x, y) VALUES (1, 2)",
2781
2818
  "INSERT INTO items (x, y) VALUES (3, 4)",
2782
2819
  "INSERT INTO items (x, y) VALUES (5, 6)",
2783
- 'COMMIT',
2784
- ]
2820
+ 'COMMIT']
2785
2821
  end
2786
- specify "should accept a columns array and a values array with slice option" do
2822
+ specify "should accept a columns array and a values array with :slice option" do
2787
2823
  @ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
2788
- @db.sqls.should == [
2789
- 'BEGIN',
2824
+ @db.sqls.should == ['BEGIN',
2790
2825
  "INSERT INTO items (x, y) VALUES (1, 2)",
2791
2826
  "INSERT INTO items (x, y) VALUES (3, 4)",
2792
2827
  'COMMIT',
2793
2828
  'BEGIN',
2794
2829
  "INSERT INTO items (x, y) VALUES (5, 6)",
2795
- 'COMMIT'
2796
- ]
2830
+ 'COMMIT']
2797
2831
  end
2798
2832
  end
2799
2833
 
2800
2834
  describe "Dataset#multi_insert" do
2801
2835
  before do
2802
- @dbc = Class.new do
2803
- attr_reader :sqls
2804
-
2805
- def execute(sql, opts={})
2806
- @sqls ||= []
2807
- @sqls << sql
2808
- end
2809
- alias execute_dui execute
2810
-
2811
- def transaction(opts={})
2812
- @sqls ||= []
2813
- @sqls << 'BEGIN'
2814
- yield
2815
- @sqls << 'COMMIT'
2816
- end
2817
- end
2818
- @db = @dbc.new
2819
-
2820
- @ds = Sequel::Dataset.new(@db).from(:items)
2821
-
2836
+ @db = Sequel.mock(:servers=>{:s1=>{}})
2837
+ @ds = @db[:items]
2822
2838
  @list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
2823
2839
  end
2824
2840
 
2841
+ specify "should return nil without a query if no values" do
2842
+ @ds.multi_insert([]).should == nil
2843
+ @db.sqls.should == []
2844
+ end
2845
+
2825
2846
  specify "should issue multiple insert statements inside a transaction" do
2826
2847
  @ds.multi_insert(@list)
2827
- @db.sqls.should == [
2828
- 'BEGIN',
2848
+ @db.sqls.should == ['BEGIN',
2849
+ "INSERT INTO items (name) VALUES ('abc')",
2850
+ "INSERT INTO items (name) VALUES ('def')",
2851
+ "INSERT INTO items (name) VALUES ('ghi')",
2852
+ 'COMMIT']
2853
+ end
2854
+
2855
+ specify "should respect :server option" do
2856
+ @ds.multi_insert(@list, :server=>:s1)
2857
+ @db.sqls.should == ['BEGIN -- s1',
2858
+ "INSERT INTO items (name) VALUES ('abc') -- s1",
2859
+ "INSERT INTO items (name) VALUES ('def') -- s1",
2860
+ "INSERT INTO items (name) VALUES ('ghi') -- s1",
2861
+ 'COMMIT -- s1']
2862
+ end
2863
+
2864
+ specify "should respect existing :server option on dataset" do
2865
+ @ds.server(:s1).multi_insert(@list)
2866
+ @db.sqls.should == ['BEGIN -- s1',
2867
+ "INSERT INTO items (name) VALUES ('abc') -- s1",
2868
+ "INSERT INTO items (name) VALUES ('def') -- s1",
2869
+ "INSERT INTO items (name) VALUES ('ghi') -- s1",
2870
+ 'COMMIT -- s1']
2871
+ end
2872
+
2873
+ specify "should respect :return=>:primary_key option" do
2874
+ @db.autoid = 1
2875
+ @ds.multi_insert(@list, :return=>:primary_key).should == [1, 2, 3]
2876
+ @db.sqls.should == ['BEGIN',
2829
2877
  "INSERT INTO items (name) VALUES ('abc')",
2830
2878
  "INSERT INTO items (name) VALUES ('def')",
2831
2879
  "INSERT INTO items (name) VALUES ('ghi')",
2832
- 'COMMIT'
2833
- ]
2880
+ 'COMMIT']
2834
2881
  end
2835
2882
 
2836
2883
  specify "should handle different formats for tables" do
2837
2884
  @ds = @ds.from(:sch__tab)
2838
2885
  @ds.multi_insert(@list)
2839
- @db.sqls.should == [
2840
- 'BEGIN',
2886
+ @db.sqls.should == ['BEGIN',
2841
2887
  "INSERT INTO sch.tab (name) VALUES ('abc')",
2842
2888
  "INSERT INTO sch.tab (name) VALUES ('def')",
2843
2889
  "INSERT INTO sch.tab (name) VALUES ('ghi')",
2844
- 'COMMIT'
2845
- ]
2846
- @db.sqls.clear
2890
+ 'COMMIT']
2847
2891
 
2848
2892
  @ds = @ds.from(:tab.qualify(:sch))
2849
2893
  @ds.multi_insert(@list)
2850
- @db.sqls.should == [
2851
- 'BEGIN',
2894
+ @db.sqls.should == ['BEGIN',
2852
2895
  "INSERT INTO sch.tab (name) VALUES ('abc')",
2853
2896
  "INSERT INTO sch.tab (name) VALUES ('def')",
2854
2897
  "INSERT INTO sch.tab (name) VALUES ('ghi')",
2855
- 'COMMIT'
2856
- ]
2857
- @db.sqls.clear
2898
+ 'COMMIT']
2899
+
2858
2900
  @ds = @ds.from(:sch__tab.identifier)
2859
2901
  @ds.multi_insert(@list)
2860
- @db.sqls.should == [
2861
- 'BEGIN',
2902
+ @db.sqls.should == ['BEGIN',
2862
2903
  "INSERT INTO sch__tab (name) VALUES ('abc')",
2863
2904
  "INSERT INTO sch__tab (name) VALUES ('def')",
2864
2905
  "INSERT INTO sch__tab (name) VALUES ('ghi')",
2865
- 'COMMIT'
2866
- ]
2906
+ 'COMMIT']
2867
2907
  end
2868
2908
 
2869
2909
  specify "should accept the :commit_every option for committing every x records" do
2870
2910
  @ds.multi_insert(@list, :commit_every => 1)
2871
- @db.sqls.should == [
2872
- 'BEGIN',
2911
+ @db.sqls.should == ['BEGIN',
2873
2912
  "INSERT INTO items (name) VALUES ('abc')",
2874
2913
  'COMMIT',
2875
2914
  'BEGIN',
@@ -2877,36 +2916,31 @@ describe "Dataset#multi_insert" do
2877
2916
  'COMMIT',
2878
2917
  'BEGIN',
2879
2918
  "INSERT INTO items (name) VALUES ('ghi')",
2880
- 'COMMIT'
2881
- ]
2919
+ 'COMMIT']
2882
2920
  end
2883
2921
 
2884
2922
  specify "should accept the :slice option for committing every x records" do
2885
2923
  @ds.multi_insert(@list, :slice => 2)
2886
- @db.sqls.should == [
2887
- 'BEGIN',
2924
+ @db.sqls.should == ['BEGIN',
2888
2925
  "INSERT INTO items (name) VALUES ('abc')",
2889
2926
  "INSERT INTO items (name) VALUES ('def')",
2890
2927
  'COMMIT',
2891
2928
  'BEGIN',
2892
2929
  "INSERT INTO items (name) VALUES ('ghi')",
2893
- 'COMMIT'
2894
- ]
2930
+ 'COMMIT']
2895
2931
  end
2896
2932
 
2897
2933
  specify "should accept string keys as column names" do
2898
2934
  @ds.multi_insert([{'x'=>1, 'y'=>2}, {'x'=>3, 'y'=>4}])
2899
- @db.sqls.should == [
2900
- 'BEGIN',
2901
- "INSERT INTO items (x, y) VALUES (1, 2)",
2902
- "INSERT INTO items (x, y) VALUES (3, 4)",
2903
- 'COMMIT'
2904
- ]
2935
+ sqls = @db.sqls
2936
+ ["INSERT INTO items (x, y) VALUES (1, 2)", "INSERT INTO items (y, x) VALUES (2, 1)"].should include(sqls.slice!(1))
2937
+ ["INSERT INTO items (x, y) VALUES (3, 4)", "INSERT INTO items (y, x) VALUES (4, 3)"].should include(sqls.slice!(1))
2938
+ sqls.should == ['BEGIN', 'COMMIT']
2905
2939
  end
2906
2940
 
2907
2941
  specify "should not do anything if no hashes are provided" do
2908
2942
  @ds.multi_insert([])
2909
- @db.sqls.should be_nil
2943
+ @db.sqls.should == []
2910
2944
  end
2911
2945
  end
2912
2946
 
@@ -2951,41 +2985,21 @@ describe "Dataset" do
2951
2985
  end
2952
2986
 
2953
2987
  specify "should support chaining of bang methods" do
2954
- @d.order!(:y)
2955
- @d.filter!(:y => 1)
2956
- @d.sql.should == "SELECT * FROM x WHERE (y = 1) ORDER BY y"
2988
+ @d.order!(:y).filter!(:y => 1).sql.should == "SELECT * FROM x WHERE (y = 1) ORDER BY y"
2957
2989
  end
2958
2990
  end
2959
2991
 
2960
2992
  describe "Dataset#to_csv" do
2961
2993
  before do
2962
- @c = Class.new(Sequel::Dataset) do
2963
- attr_accessor :data
2964
- attr_accessor :columns
2965
-
2966
- def fetch_rows(sql, &block)
2967
- @data.each(&block)
2968
- end
2969
-
2970
- # naked should return self here because to_csv wants a naked result set.
2971
- def naked
2972
- self
2973
- end
2974
- end
2975
-
2976
- @ds = @c.new(nil).from(:items)
2977
- @ds.columns = [:a, :b, :c]
2978
- @ds.data = [ {:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}, {:a=>7, :b=>8, :c=>9} ]
2994
+ @ds = Sequel.mock(:fetch=>[{:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}, {:a=>7, :b=>8, :c=>9}])[:items].columns(:a, :b, :c)
2979
2995
  end
2980
2996
 
2981
2997
  specify "should format a CSV representation of the records" do
2982
- @ds.to_csv.should ==
2983
- "a, b, c\r\n1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2998
+ @ds.to_csv.should == "a, b, c\r\n1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2984
2999
  end
2985
3000
 
2986
3001
  specify "should exclude column titles if so specified" do
2987
- @ds.to_csv(false).should ==
2988
- "1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
3002
+ @ds.to_csv(false).should == "1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2989
3003
  end
2990
3004
  end
2991
3005
 
@@ -3083,66 +3097,40 @@ describe "Dataset#insert_sql" do
3083
3097
  end
3084
3098
  end
3085
3099
 
3086
- class DummyMummyDataset < Sequel::Dataset
3087
- def first
3088
- raise if @opts[:from] == [:a]
3089
- true
3100
+ describe "Dataset#inspect" do
3101
+ before do
3102
+ class ::InspectDataset < Sequel::Dataset; end
3090
3103
  end
3091
- end
3092
-
3093
- class DummyMummyDatabase < Sequel::Database
3094
- attr_reader :sqls
3095
-
3096
- def execute(sql)
3097
- @sqls ||= []
3098
- @sqls << sql
3104
+ after do
3105
+ Object.send(:remove_const, :InspectDataset) if defined?(::InspectDataset)
3099
3106
  end
3100
-
3101
- def transaction; yield; end
3102
3107
 
3103
- def dataset
3104
- DummyMummyDataset.new(self)
3108
+ specify "should include the class name and the corresponding SQL statement" do
3109
+ Sequel::Dataset.new(nil).from(:blah).inspect.should == '#<Sequel::Dataset: "SELECT * FROM blah">'
3110
+ InspectDataset.new(nil).from(:blah).inspect.should == '#<InspectDataset: "SELECT * FROM blah">'
3105
3111
  end
3106
- end
3107
3112
 
3108
- describe "Dataset#inspect" do
3109
- before do
3110
- @ds = Sequel::Dataset.new(nil).from(:blah)
3111
- end
3112
-
3113
- specify "should include the class name and the corresponding SQL statement" do
3114
- @ds.inspect.should == '#<%s: %s>' % [@ds.class.to_s, @ds.sql.inspect]
3113
+ specify "should skip anonymous classes" do
3114
+ Class.new(Class.new(Sequel::Dataset)).new(nil).from(:blah).inspect.should == '#<Sequel::Dataset: "SELECT * FROM blah">'
3115
+ Class.new(InspectDataset).new(nil).from(:blah).inspect.should == '#<InspectDataset: "SELECT * FROM blah">'
3115
3116
  end
3116
3117
  end
3117
3118
 
3118
3119
  describe "Dataset#all" do
3119
3120
  before do
3120
- @c = Class.new(Sequel::Dataset) do
3121
- def fetch_rows(sql, &block)
3122
- block.call({:x => 1, :y => 2})
3123
- block.call({:x => 3, :y => 4})
3124
- block.call(sql)
3125
- end
3126
- end
3127
- @dataset = @c.new(nil).from(:items)
3121
+ @dataset = Sequel.mock(:fetch=>[{:x => 1, :y => 2}, {:x => 3, :y => 4}])[:items]
3128
3122
  end
3129
3123
 
3130
3124
  specify "should return an array with all records" do
3131
- @dataset.all.should == [
3132
- {:x => 1, :y => 2},
3133
- {:x => 3, :y => 4},
3134
- "SELECT * FROM items"
3135
- ]
3125
+ @dataset.all.should == [{:x => 1, :y => 2}, {:x => 3, :y => 4}]
3126
+ @dataset.db.sqls.should == ["SELECT * FROM items"]
3136
3127
  end
3137
3128
 
3138
3129
  specify "should iterate over the array if a block is given" do
3139
3130
  a = []
3140
-
3141
- @dataset.all do |r|
3142
- a << (r.is_a?(Hash) ? r[:x] : r)
3143
- end
3144
-
3145
- a.should == [1, 3, "SELECT * FROM items"]
3131
+ @dataset.all{|r| a << r.values_at(:x, :y)}.should == [{:x => 1, :y => 2}, {:x => 3, :y => 4}]
3132
+ a.should == [[1, 2], [3, 4]]
3133
+ @dataset.db.sqls.should == ["SELECT * FROM items"]
3146
3134
  end
3147
3135
  end
3148
3136
 
@@ -3152,75 +3140,60 @@ describe "Dataset#grep" do
3152
3140
  end
3153
3141
 
3154
3142
  specify "should format a SQL filter correctly" do
3155
- @ds.grep(:title, 'ruby').sql.should ==
3156
- "SELECT * FROM posts WHERE ((title LIKE 'ruby'))"
3143
+ @ds.grep(:title, 'ruby').sql.should == "SELECT * FROM posts WHERE ((title LIKE 'ruby'))"
3157
3144
  end
3158
3145
 
3159
3146
  specify "should support multiple columns" do
3160
- @ds.grep([:title, :body], 'ruby').sql.should ==
3161
- "SELECT * FROM posts WHERE ((title LIKE 'ruby') OR (body LIKE 'ruby'))"
3147
+ @ds.grep([:title, :body], 'ruby').sql.should == "SELECT * FROM posts WHERE ((title LIKE 'ruby') OR (body LIKE 'ruby'))"
3162
3148
  end
3163
3149
 
3164
3150
  specify "should support multiple search terms" do
3165
- @ds.grep(:title, ['abc', 'def']).sql.should ==
3166
- "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def'))"
3151
+ @ds.grep(:title, ['abc', 'def']).sql.should == "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def'))"
3167
3152
  end
3168
3153
 
3169
3154
  specify "should support multiple columns and search terms" do
3170
- @ds.grep([:title, :body], ['abc', 'def']).sql.should ==
3171
- "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def') OR (body LIKE 'abc') OR (body LIKE 'def'))"
3155
+ @ds.grep([:title, :body], ['abc', 'def']).sql.should == "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def') OR (body LIKE 'abc') OR (body LIKE 'def'))"
3172
3156
  end
3173
3157
 
3174
3158
  specify "should support the :all_patterns option" do
3175
- @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true).sql.should ==
3176
- "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (body LIKE 'abc')) AND ((title LIKE 'def') OR (body LIKE 'def')))"
3159
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true).sql.should == "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (body LIKE 'abc')) AND ((title LIKE 'def') OR (body LIKE 'def')))"
3177
3160
  end
3178
3161
 
3179
3162
  specify "should support the :all_columns option" do
3180
- @ds.grep([:title, :body], ['abc', 'def'], :all_columns=>true).sql.should ==
3181
- "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (title LIKE 'def')) AND ((body LIKE 'abc') OR (body LIKE 'def')))"
3163
+ @ds.grep([:title, :body], ['abc', 'def'], :all_columns=>true).sql.should == "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (title LIKE 'def')) AND ((body LIKE 'abc') OR (body LIKE 'def')))"
3182
3164
  end
3183
3165
 
3184
3166
  specify "should support the :case_insensitive option" do
3185
- @ds.grep([:title, :body], ['abc', 'def'], :case_insensitive=>true).sql.should ==
3186
- "SELECT * FROM posts WHERE ((title ILIKE 'abc') OR (title ILIKE 'def') OR (body ILIKE 'abc') OR (body ILIKE 'def'))"
3167
+ @ds.grep([:title, :body], ['abc', 'def'], :case_insensitive=>true).sql.should == "SELECT * FROM posts WHERE ((title ILIKE 'abc') OR (title ILIKE 'def') OR (body ILIKE 'abc') OR (body ILIKE 'def'))"
3187
3168
  end
3188
3169
 
3189
3170
  specify "should support the :all_patterns and :all_columns options together" do
3190
- @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :all_columns=>true).sql.should ==
3191
- "SELECT * FROM posts WHERE ((title LIKE 'abc') AND (body LIKE 'abc') AND (title LIKE 'def') AND (body LIKE 'def'))"
3171
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :all_columns=>true).sql.should == "SELECT * FROM posts WHERE ((title LIKE 'abc') AND (body LIKE 'abc') AND (title LIKE 'def') AND (body LIKE 'def'))"
3192
3172
  end
3193
3173
 
3194
3174
  specify "should support the :all_patterns and :case_insensitive options together" do
3195
- @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :case_insensitive=>true).sql.should ==
3196
- "SELECT * FROM posts WHERE (((title ILIKE 'abc') OR (body ILIKE 'abc')) AND ((title ILIKE 'def') OR (body ILIKE 'def')))"
3175
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :case_insensitive=>true).sql.should == "SELECT * FROM posts WHERE (((title ILIKE 'abc') OR (body ILIKE 'abc')) AND ((title ILIKE 'def') OR (body ILIKE 'def')))"
3197
3176
  end
3198
3177
 
3199
3178
  specify "should support the :all_columns and :case_insensitive options together" do
3200
- @ds.grep([:title, :body], ['abc', 'def'], :all_columns=>true, :case_insensitive=>true).sql.should ==
3201
- "SELECT * FROM posts WHERE (((title ILIKE 'abc') OR (title ILIKE 'def')) AND ((body ILIKE 'abc') OR (body ILIKE 'def')))"
3179
+ @ds.grep([:title, :body], ['abc', 'def'], :all_columns=>true, :case_insensitive=>true).sql.should == "SELECT * FROM posts WHERE (((title ILIKE 'abc') OR (title ILIKE 'def')) AND ((body ILIKE 'abc') OR (body ILIKE 'def')))"
3202
3180
  end
3203
3181
 
3204
3182
  specify "should support the :all_patterns, :all_columns, and :case_insensitive options together" do
3205
- @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :all_columns=>true, :case_insensitive=>true).sql.should ==
3206
- "SELECT * FROM posts WHERE ((title ILIKE 'abc') AND (body ILIKE 'abc') AND (title ILIKE 'def') AND (body ILIKE 'def'))"
3183
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :all_columns=>true, :case_insensitive=>true).sql.should == "SELECT * FROM posts WHERE ((title ILIKE 'abc') AND (body ILIKE 'abc') AND (title ILIKE 'def') AND (body ILIKE 'def'))"
3207
3184
  end
3208
3185
 
3209
3186
  specify "should support regexps though the database may not support it" do
3210
- @ds.grep(:title, /ruby/).sql.should ==
3211
- "SELECT * FROM posts WHERE ((title ~ 'ruby'))"
3212
-
3213
- @ds.grep(:title, [/^ruby/, 'ruby']).sql.should ==
3214
- "SELECT * FROM posts WHERE ((title ~ '^ruby') OR (title LIKE 'ruby'))"
3187
+ @ds.grep(:title, /ruby/).sql.should == "SELECT * FROM posts WHERE ((title ~ 'ruby'))"
3188
+ @ds.grep(:title, [/^ruby/, 'ruby']).sql.should == "SELECT * FROM posts WHERE ((title ~ '^ruby') OR (title LIKE 'ruby'))"
3215
3189
  end
3216
3190
 
3217
3191
  specify "should support searching against other columns" do
3218
- @ds.grep(:title, :body).sql.should ==
3219
- "SELECT * FROM posts WHERE ((title LIKE body))"
3192
+ @ds.grep(:title, :body).sql.should == "SELECT * FROM posts WHERE ((title LIKE body))"
3220
3193
  end
3221
3194
  end
3222
3195
 
3223
- describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #execute" do
3196
+ describe "Dataset default #fetch_rows, #insert, #update, #delete, #with_sql_delete, #truncate, #execute" do
3224
3197
  before do
3225
3198
  @db = Sequel::Database.new
3226
3199
  @ds = @db[:items]
@@ -3237,6 +3210,14 @@ describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #ex
3237
3210
  @ds.delete
3238
3211
  end
3239
3212
 
3213
+ specify "#with_sql_delete should execute delete SQL" do
3214
+ sql = 'DELETE FROM foo'
3215
+ @db.should_receive(:execute).once.with(sql, :server=>:default)
3216
+ @ds.with_sql_delete(sql)
3217
+ @db.should_receive(:execute_dui).once.with(sql, :server=>:default)
3218
+ @ds.with_sql_delete(sql)
3219
+ end
3220
+
3240
3221
  specify "#insert should execute insert SQL" do
3241
3222
  @db.should_receive(:execute).once.with('INSERT INTO items DEFAULT VALUES', :server=>:default)
3242
3223
  @ds.insert([])
@@ -3260,6 +3241,7 @@ describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #ex
3260
3241
 
3261
3242
  specify "#truncate should raise an InvalidOperation exception if the dataset is filtered" do
3262
3243
  proc{@ds.filter(:a=>1).truncate}.should raise_error(Sequel::InvalidOperation)
3244
+ proc{@ds.having(:a=>1).truncate}.should raise_error(Sequel::InvalidOperation)
3263
3245
  end
3264
3246
 
3265
3247
  specify "#execute should execute the SQL on the database" do
@@ -3270,58 +3252,80 @@ end
3270
3252
 
3271
3253
  describe "Dataset prepared statements and bound variables " do
3272
3254
  before do
3273
- @db = Sequel::Database.new
3274
- @db.meta_def(:sqls){@sqls||=[]}
3275
- def @db.execute(sql, opts={})
3276
- sqls << sql
3277
- end
3278
- def @db.dataset
3279
- ds = super()
3280
- def ds.fetch_rows(sql, &block)
3281
- execute(sql)
3282
- end
3283
- ds
3284
- end
3255
+ @db = Sequel.mock
3285
3256
  @ds = @db[:items]
3257
+ @ds.meta_def(:insert_sql){|*v| "#{super(*v)}#{' RETURNING *' if opts.has_key?(:returning)}" }
3286
3258
  end
3287
3259
 
3288
3260
  specify "#call should take a type and bind hash and interpolate it" do
3261
+ @ds.filter(:num=>:$n).call(:each, :n=>1)
3289
3262
  @ds.filter(:num=>:$n).call(:select, :n=>1)
3263
+ @ds.filter(:num=>:$n).call([:map, :a], :n=>1)
3264
+ @ds.filter(:num=>:$n).call([:to_hash, :a, :b], :n=>1)
3265
+ @ds.filter(:num=>:$n).call([:to_hash_groups, :a, :b], :n=>1)
3290
3266
  @ds.filter(:num=>:$n).call(:first, :n=>1)
3291
3267
  @ds.filter(:num=>:$n).call(:delete, :n=>1)
3292
3268
  @ds.filter(:num=>:$n).call(:update, {:n=>1, :n2=>2}, :num=>:$n2)
3293
3269
  @ds.call(:insert, {:n=>1}, :num=>:$n)
3294
- @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3270
+ @ds.call(:insert_select, {:n=>1}, :num=>:$n)
3271
+ @db.sqls.should == [
3272
+ 'SELECT * FROM items WHERE (num = 1)',
3273
+ 'SELECT * FROM items WHERE (num = 1)',
3274
+ 'SELECT * FROM items WHERE (num = 1)',
3275
+ 'SELECT * FROM items WHERE (num = 1)',
3276
+ 'SELECT * FROM items WHERE (num = 1)',
3295
3277
  'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3296
3278
  'DELETE FROM items WHERE (num = 1)',
3297
3279
  'UPDATE items SET num = 2 WHERE (num = 1)',
3298
- 'INSERT INTO items (num) VALUES (1)']
3280
+ 'INSERT INTO items (num) VALUES (1)',
3281
+ 'INSERT INTO items (num) VALUES (1) RETURNING *']
3299
3282
  end
3300
3283
 
3301
3284
  specify "#prepare should take a type and name and store it in the database for later use with call" do
3302
3285
  pss = []
3286
+ pss << @ds.filter(:num=>:$n).prepare(:each, :en)
3303
3287
  pss << @ds.filter(:num=>:$n).prepare(:select, :sn)
3288
+ pss << @ds.filter(:num=>:$n).prepare([:map, :a], :sm)
3289
+ pss << @ds.filter(:num=>:$n).prepare([:to_hash, :a, :b], :sh)
3290
+ pss << @ds.filter(:num=>:$n).prepare([:to_hash_groups, :a, :b], :shg)
3304
3291
  pss << @ds.filter(:num=>:$n).prepare(:first, :fn)
3305
3292
  pss << @ds.filter(:num=>:$n).prepare(:delete, :dn)
3306
3293
  pss << @ds.filter(:num=>:$n).prepare(:update, :un, :num=>:$n2)
3307
3294
  pss << @ds.prepare(:insert, :in, :num=>:$n)
3308
- @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :sn, :un]
3309
- [:sn, :fn, :dn, :un, :in].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3295
+ pss << @ds.prepare(:insert_select, :ins, :num=>:$n)
3296
+ @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :en, :fn, :in, :ins, :sh, :shg, :sm, :sn, :un]
3297
+ [:en, :sn, :sm, :sh, :shg, :fn, :dn, :un, :in, :ins].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3298
+ @db.call(:en, :n=>1){}
3310
3299
  @db.call(:sn, :n=>1)
3300
+ @db.call(:sm, :n=>1)
3301
+ @db.call(:sh, :n=>1)
3302
+ @db.call(:shg, :n=>1)
3311
3303
  @db.call(:fn, :n=>1)
3312
3304
  @db.call(:dn, :n=>1)
3313
3305
  @db.call(:un, :n=>1, :n2=>2)
3314
3306
  @db.call(:in, :n=>1)
3315
- @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3307
+ @db.call(:ins, :n=>1)
3308
+ @db.sqls.should == [
3309
+ 'SELECT * FROM items WHERE (num = 1)',
3310
+ 'SELECT * FROM items WHERE (num = 1)',
3311
+ 'SELECT * FROM items WHERE (num = 1)',
3312
+ 'SELECT * FROM items WHERE (num = 1)',
3313
+ 'SELECT * FROM items WHERE (num = 1)',
3316
3314
  'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3317
3315
  'DELETE FROM items WHERE (num = 1)',
3318
3316
  'UPDATE items SET num = 2 WHERE (num = 1)',
3319
- 'INSERT INTO items (num) VALUES (1)']
3317
+ 'INSERT INTO items (num) VALUES (1)',
3318
+ 'INSERT INTO items (num) VALUES (1) RETURNING *']
3320
3319
  end
3321
3320
 
3321
+ specify "#call should default to using :all if an invalid type is given" do
3322
+ @ds.filter(:num=>:$n).call(:select_all, :n=>1)
3323
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3324
+ end
3325
+
3322
3326
  specify "#inspect should indicate it is a prepared statement with the prepared SQL" do
3323
3327
  @ds.filter(:num=>:$n).prepare(:select, :sn).inspect.should == \
3324
- '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
3328
+ '<Sequel::Mock::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
3325
3329
  end
3326
3330
 
3327
3331
  specify "should handle literal strings" do
@@ -3329,6 +3333,13 @@ describe "Dataset prepared statements and bound variables " do
3329
3333
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3330
3334
  end
3331
3335
 
3336
+ specify "should handle columns on prepared statements correctly" do
3337
+ @db.columns = [:num]
3338
+ @ds.meta_def(:select_where_sql){|sql| super(sql); sql << " OR #{columns.first} = 1" if opts[:where]}
3339
+ @ds.filter(:num=>:$n).prepare(:select, :sn).sql.should == 'SELECT * FROM items WHERE (num = $n) OR num = 1'
3340
+ @db.sqls.should == ['SELECT * FROM items LIMIT 1']
3341
+ end
3342
+
3332
3343
  specify "should handle datasets using static sql and placeholders" do
3333
3344
  @db["SELECT * FROM items WHERE (num = ?)", :$n].call(:select, :n=>1)
3334
3345
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
@@ -3357,23 +3368,16 @@ end
3357
3368
 
3358
3369
  describe Sequel::Dataset::UnnumberedArgumentMapper do
3359
3370
  before do
3360
- @db = Sequel::Database.new
3361
- @db.meta_def(:sqls){@sqls||=[]}
3362
- def @db.execute(sql, opts={})
3363
- sqls << [sql, *opts[:arguments]]
3364
- end
3371
+ @db = Sequel.mock
3365
3372
  @ds = @db[:items].filter(:num=>:$n)
3366
- def @ds.fetch_rows(sql, &block)
3367
- execute(sql)
3368
- end
3369
3373
  def @ds.execute(sql, opts={}, &block)
3370
- @db.execute(sql, {:arguments=>bind_arguments}.merge(opts))
3374
+ super(sql, opts.merge({:arguments=>bind_arguments}), &block)
3371
3375
  end
3372
- def @ds.execute_dui(*args, &block)
3373
- execute(*args, &block)
3376
+ def @ds.execute_dui(sql, opts={}, &block)
3377
+ super(sql, opts.merge({:arguments=>bind_arguments}), &block)
3374
3378
  end
3375
- def @ds.execute_insert(*args, &block)
3376
- execute(*args, &block)
3379
+ def @ds.execute_insert(sql, opts={}, &block)
3380
+ super(sql, opts.merge({:arguments=>bind_arguments}), &block)
3377
3381
  end
3378
3382
  @ps = []
3379
3383
  @ps << @ds.prepare(:select, :s)
@@ -3386,64 +3390,51 @@ describe Sequel::Dataset::UnnumberedArgumentMapper do
3386
3390
  end
3387
3391
 
3388
3392
  specify "#inspect should show the actual SQL submitted to the database" do
3389
- @ps.first.inspect.should == '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
3393
+ @ps.first.inspect.should == '<Sequel::Mock::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
3390
3394
  end
3391
3395
 
3392
- specify "should submitted the SQL to the database with placeholders and bind variables" do
3396
+ specify "should submit the SQL to the database with placeholders and bind variables" do
3393
3397
  @ps.each{|p| p.prepared_sql; p.call(:n=>1)}
3394
- @db.sqls.should == [["SELECT * FROM items WHERE (num = ?)", 1],
3395
- ["SELECT * FROM items WHERE (num = ?)", 1],
3396
- ["SELECT * FROM items WHERE (num = ?) LIMIT 1", 1],
3397
- ["DELETE FROM items WHERE (num = ?)", 1],
3398
- ["INSERT INTO items (num) VALUES (?)", 1],
3399
- ["UPDATE items SET num = ? WHERE (num = ?)", 1, 1],
3400
- ]
3398
+ @db.sqls.should == ["SELECT * FROM items WHERE (num = ?) -- args: [1]",
3399
+ "SELECT * FROM items WHERE (num = ?) -- args: [1]",
3400
+ "SELECT * FROM items WHERE (num = ?) LIMIT 1 -- args: [1]",
3401
+ "DELETE FROM items WHERE (num = ?) -- args: [1]",
3402
+ "INSERT INTO items (num) VALUES (?) -- args: [1]",
3403
+ "UPDATE items SET num = ? WHERE (num = ?) -- args: [1, 1]"]
3404
+ end
3405
+
3406
+ specify "should handle unrecognized statement types as :all" do
3407
+ ps = @ds.prepare(:select_all, :s)
3408
+ ps.extend(Sequel::Dataset::UnnumberedArgumentMapper)
3409
+ ps.prepared_sql
3410
+ ps.call(:n=>1)
3411
+ @db.sqls.should == ["SELECT * FROM items WHERE (num = ?) -- args: [1]"]
3401
3412
  end
3402
3413
  end
3403
3414
 
3404
3415
  describe "Sequel::Dataset#server" do
3405
3416
  specify "should set the server to use for the dataset" do
3406
- @db = Sequel::Database.new
3417
+ @db = Sequel.mock(:servers=>{:s=>{}, :i=>{}, :d=>{}, :u=>{}})
3407
3418
  @ds = @db[:items].server(:s)
3408
- sqls = []
3409
- @db.meta_def(:execute) do |sql, opts|
3410
- sqls << [sql, opts[:server]]
3411
- end
3412
- def @ds.fetch_rows(sql, &block)
3413
- execute(sql)
3414
- end
3415
3419
  @ds.all
3416
3420
  @ds.server(:i).insert(:a=>1)
3417
3421
  @ds.server(:d).delete
3418
3422
  @ds.server(:u).update(:a=>:a+1)
3419
- sqls.should == [['SELECT * FROM items', :s],
3420
- ['INSERT INTO items (a) VALUES (1)', :i],
3421
- ['DELETE FROM items', :d],
3422
- ['UPDATE items SET a = (a + 1)', :u]]
3423
+ @db.sqls.should == ['SELECT * FROM items -- s', 'INSERT INTO items (a) VALUES (1) -- i', 'DELETE FROM items -- d', 'UPDATE items SET a = (a + 1) -- u']
3423
3424
  end
3424
3425
  end
3425
3426
 
3426
3427
  describe "Sequel::Dataset#each_server" do
3427
- before do
3428
- @db = Sequel::Database.new(:servers=>{:s=>{}, :i=>{}})
3429
- @ds = @db[:items]
3430
- sqls = @sqls = []
3431
- @db.meta_def(:execute) do |sql, opts|
3432
- sqls << [sql, opts[:server]]
3433
- end
3434
- def @ds.fetch_rows(sql, &block)
3435
- execute(sql)
3436
- end
3437
- end
3438
-
3439
3428
  specify "should yield a dataset for each server" do
3429
+ @db = Sequel.mock(:servers=>{:s=>{}, :i=>{}})
3430
+ @ds = @db[:items]
3440
3431
  @ds.each_server do |ds|
3441
3432
  ds.should be_a_kind_of(Sequel::Dataset)
3442
3433
  ds.should_not == @ds
3443
3434
  ds.sql.should == @ds.sql
3444
3435
  ds.all
3445
3436
  end
3446
- @sqls.sort_by{|sql, s| s.to_s}.should == [['SELECT * FROM items', :default], ['SELECT * FROM items', :i], ['SELECT * FROM items', :s]]
3437
+ @db.sqls.sort.should == ['SELECT * FROM items', 'SELECT * FROM items -- i', 'SELECT * FROM items -- s']
3447
3438
  end
3448
3439
  end
3449
3440
 
@@ -3493,23 +3484,23 @@ end
3493
3484
 
3494
3485
  describe "Sequel::Dataset#qualify" do
3495
3486
  specify "should qualify to the given table" do
3496
- MockDatabase.new[:t].filter{a<b}.qualify(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3487
+ Sequel::Dataset.new(nil).from(:t).filter{a<b}.qualify(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3497
3488
  end
3498
3489
 
3499
3490
  specify "should qualify to the first source if no table if given" do
3500
- MockDatabase.new[:t].filter{a<b}.qualify.sql.should == 'SELECT t.* FROM t WHERE (t.a < t.b)'
3491
+ Sequel::Dataset.new(nil).from(:t).filter{a<b}.qualify.sql.should == 'SELECT t.* FROM t WHERE (t.a < t.b)'
3501
3492
  end
3502
3493
  end
3503
3494
 
3504
3495
  describe "Sequel::Dataset#qualify_to" do
3505
3496
  specify "should qualify to the given table" do
3506
- MockDatabase.new[:t].filter{a<b}.qualify_to(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3497
+ Sequel::Dataset.new(nil).from(:t).filter{a<b}.qualify_to(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3507
3498
  end
3508
3499
  end
3509
3500
 
3510
3501
  describe "Sequel::Dataset#qualify_to_first_source" do
3511
3502
  before do
3512
- @ds = MockDatabase.new[:t]
3503
+ @ds = Sequel::Database.new[:t]
3513
3504
  end
3514
3505
 
3515
3506
  specify "should qualify_to the first source" do
@@ -3519,13 +3510,11 @@ describe "Sequel::Dataset#qualify_to_first_source" do
3519
3510
  end
3520
3511
 
3521
3512
  specify "should handle the select, order, where, having, and group options/clauses" do
3522
- @ds.select(:a).filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == \
3523
- 'SELECT t.a FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
3513
+ @ds.select(:a).filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == 'SELECT t.a FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
3524
3514
  end
3525
3515
 
3526
3516
  specify "should handle the select using a table.* if all columns are currently selected" do
3527
- @ds.filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == \
3528
- 'SELECT t.* FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
3517
+ @ds.filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
3529
3518
  end
3530
3519
 
3531
3520
  specify "should handle hashes in select option" do
@@ -3588,20 +3577,97 @@ describe "Sequel::Dataset#qualify_to_first_source" do
3588
3577
  @ds.filter(':a > :b', :a=>:c, :b=>1).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.c > 1)'
3589
3578
  end
3590
3579
 
3580
+ specify "should handle SQL::Wrappers" do
3581
+ @ds.filter(Sequel::SQL::Wrapper.new(:a)).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE t.a'
3582
+ end
3583
+
3591
3584
  specify "should handle SQL::WindowFunctions" do
3592
3585
  @ds.meta_def(:supports_window_functions?){true}
3593
3586
  @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'
3594
3587
  end
3595
3588
 
3596
3589
  specify "should handle all other objects by returning them unchanged" do
3597
- @ds.select("a").filter{a(3)}.filter('blah').order('true'.lit).group('a > ?'.lit(1)).having(false).qualify_to_first_source.sql.should == \
3598
- "SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a > 1 HAVING 'f' ORDER BY true"
3590
+ @ds.select("a").filter{a(3)}.filter('blah').order('true'.lit).group('a > ?'.lit(1)).having(false).qualify_to_first_source.sql.should == "SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a > 1 HAVING 'f' ORDER BY true"
3591
+ end
3592
+ end
3593
+
3594
+ describe "Sequel::Dataset#unbind" do
3595
+ before do
3596
+ @ds = Sequel::Database.new[:t]
3597
+ @u = proc{|ds| ds, bv = ds.unbind; [ds.sql, bv]}
3598
+ end
3599
+
3600
+ specify "should unbind values assigned to equality and inequality statements" do
3601
+ @ds.filter(:foo=>1).unbind.first.sql.should == "SELECT * FROM t WHERE (foo = $foo)"
3602
+ @ds.exclude(:foo=>1).unbind.first.sql.should == "SELECT * FROM t WHERE (foo != $foo)"
3603
+ @ds.filter{foo > 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo > $foo)"
3604
+ @ds.filter{foo >= 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo >= $foo)"
3605
+ @ds.filter{foo < 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo < $foo)"
3606
+ @ds.filter{foo <= 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo <= $foo)"
3607
+ end
3608
+
3609
+ specify "should return variables that could be used bound to recreate the previous query" do
3610
+ @ds.filter(:foo=>1).unbind.last.should == {:foo=>1}
3611
+ @ds.exclude(:foo=>1).unbind.last.should == {:foo=>1}
3612
+ end
3613
+
3614
+ specify "should handle numerics, strings, dates, times, and datetimes" do
3615
+ @u[@ds.filter(:foo=>1)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>1}]
3616
+ @u[@ds.filter(:foo=>1.0)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>1.0}]
3617
+ @u[@ds.filter(:foo=>BigDecimal.new('1.0'))].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>BigDecimal.new('1.0')}]
3618
+ @u[@ds.filter(:foo=>'a')].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>'a'}]
3619
+ @u[@ds.filter(:foo=>Date.today)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>Date.today}]
3620
+ t = Time.now
3621
+ @u[@ds.filter(:foo=>t)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>t}]
3622
+ dt = DateTime.now
3623
+ @u[@ds.filter(:foo=>dt)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>dt}]
3624
+ end
3625
+
3626
+ specify "should not unbind literal strings" do
3627
+ @u[@ds.filter(:foo=>'a'.lit)].should == ["SELECT * FROM t WHERE (foo = a)", {}]
3628
+ end
3629
+
3630
+ specify "should not unbind Identifiers, QualifiedIdentifiers, or Symbols used as booleans" do
3631
+ @u[@ds.filter(:foo).filter{bar}.filter{foo__bar}].should == ["SELECT * FROM t WHERE (foo AND bar AND foo.bar)", {}]
3632
+ end
3633
+
3634
+ specify "should not unbind for values it doesn't understand" do
3635
+ @u[@ds.filter(:foo=>Class.new{def sql_literal(ds) 'bar' end}.new)].should == ["SELECT * FROM t WHERE (foo = bar)", {}]
3636
+ end
3637
+
3638
+ specify "should handle QualifiedIdentifiers" do
3639
+ @u[@ds.filter{foo__bar > 1}].should == ["SELECT * FROM t WHERE (foo.bar > $foo.bar)", {:"foo.bar"=>1}]
3640
+ end
3641
+
3642
+ specify "should handle wrapped objects" do
3643
+ @u[@ds.filter{Sequel::SQL::Wrapper.new(foo__bar) > Sequel::SQL::Wrapper.new(1)}].should == ["SELECT * FROM t WHERE (foo.bar > $foo.bar)", {:"foo.bar"=>1}]
3644
+ end
3645
+
3646
+ specify "should handle deep nesting" do
3647
+ @u[@ds.filter{foo > 1}.and{bar < 2}.or(:baz=>3).and({~{:x=>4}=>true}.case(false))].should == ["SELECT * FROM t WHERE ((((foo > $foo) AND (bar < $bar)) OR (baz = $baz)) AND (CASE WHEN (x != $x) THEN 't' ELSE 'f' END))", {:foo=>1, :bar=>2, :baz=>3, :x=>4}]
3648
+ end
3649
+
3650
+ specify "should handle JOIN ON" do
3651
+ @u[@ds.cross_join(:x).join(:a, [:u]).join(:b, [[:c, :d], [:e,1]])].should == ["SELECT * FROM t CROSS JOIN x INNER JOIN a USING (u) INNER JOIN b ON ((b.c = a.d) AND (b.e = $b.e))", {:"b.e"=>1}]
3652
+ end
3653
+
3654
+ specify "should raise an UnbindDuplicate exception if same variable is used with multiple different values" do
3655
+ proc{@ds.filter(:foo=>1).or(:foo=>2).unbind}.should raise_error(Sequel::UnbindDuplicate)
3656
+ end
3657
+
3658
+ specify "should handle case where the same variable has the same value in multiple places " do
3659
+ @u[@ds.filter(:foo=>1).or(:foo=>1)].should == ["SELECT * FROM t WHERE ((foo = $foo) OR (foo = $foo))", {:foo=>1}]
3660
+ end
3661
+
3662
+ specify "should raise Error for unhandled objects inside Identifiers and QualifiedIndentifiers" do
3663
+ proc{@ds.filter(Sequel::SQL::Identifier.new([]) > 1).unbind}.should raise_error(Sequel::Error)
3664
+ proc{@ds.filter{foo.qualify({}) > 1}.unbind}.should raise_error(Sequel::Error)
3599
3665
  end
3600
3666
  end
3601
3667
 
3602
3668
  describe "Sequel::Dataset #with and #with_recursive" do
3603
3669
  before do
3604
- @db = MockDatabase.new
3670
+ @db = Sequel::Database.new
3605
3671
  @ds = @db[:t]
3606
3672
  end
3607
3673
 
@@ -3633,56 +3699,72 @@ describe "Sequel::Dataset #with and #with_recursive" do
3633
3699
  proc{@ds.with(:t, @db[:x], :args=>[:b])}.should raise_error(Sequel::Error)
3634
3700
  proc{@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c])}.should raise_error(Sequel::Error)
3635
3701
  end
3702
+
3703
+ specify "#with should work on insert, update, and delete statements if they support it" do
3704
+ [:insert, :update, :delete].each do |m|
3705
+ @ds.meta_def(:"#{m}_clause_methods"){[:"#{m}_with_sql"] + super()}
3706
+ end
3707
+ @ds.with(:t, @db[:x]).insert_sql(1).should == 'WITH t AS (SELECT * FROM x) INSERT INTO t VALUES (1)'
3708
+ @ds.with(:t, @db[:x]).update_sql(:foo=>1).should == 'WITH t AS (SELECT * FROM x) UPDATE t SET foo = 1'
3709
+ @ds.with(:t, @db[:x]).delete_sql.should == 'WITH t AS (SELECT * FROM x) DELETE FROM t'
3710
+ end
3711
+
3712
+ specify "should hoist WITH clauses in given dataset(s) if dataset doesn't support WITH in subselect" do
3713
+ @ds.meta_def(:supports_cte?){true}
3714
+ @ds.meta_def(:supports_cte_in_subselect?){false}
3715
+ @ds.with(:t, @ds.from(:s).with(:s, @ds.from(:r))).sql.should == 'WITH s AS (SELECT * FROM r), t AS (SELECT * FROM s) SELECT * FROM t'
3716
+ @ds.with_recursive(:t, @ds.from(:s).with(:s, @ds.from(:r)), @ds.from(:q).with(:q, @ds.from(:p))).sql.should == 'WITH s AS (SELECT * FROM r), q AS (SELECT * FROM p), t AS (SELECT * FROM s UNION ALL SELECT * FROM q) SELECT * FROM t'
3717
+ end
3636
3718
  end
3637
3719
 
3638
3720
  describe Sequel::SQL::Constants do
3639
3721
  before do
3640
- @db = MockDatabase.new
3722
+ @db = Sequel::Database.new
3641
3723
  end
3642
3724
 
3643
3725
  it "should have CURRENT_DATE" do
3644
- @db.literal(Sequel::SQL::Constants::CURRENT_DATE) == 'CURRENT_DATE'
3645
- @db.literal(Sequel::CURRENT_DATE) == 'CURRENT_DATE'
3726
+ @db.literal(Sequel::SQL::Constants::CURRENT_DATE).should == 'CURRENT_DATE'
3727
+ @db.literal(Sequel::CURRENT_DATE).should == 'CURRENT_DATE'
3646
3728
  end
3647
3729
 
3648
3730
  it "should have CURRENT_TIME" do
3649
- @db.literal(Sequel::SQL::Constants::CURRENT_TIME) == 'CURRENT_TIME'
3650
- @db.literal(Sequel::CURRENT_TIME) == 'CURRENT_TIME'
3731
+ @db.literal(Sequel::SQL::Constants::CURRENT_TIME).should == 'CURRENT_TIME'
3732
+ @db.literal(Sequel::CURRENT_TIME).should == 'CURRENT_TIME'
3651
3733
  end
3652
3734
 
3653
3735
  it "should have CURRENT_TIMESTAMP" do
3654
- @db.literal(Sequel::SQL::Constants::CURRENT_TIMESTAMP) == 'CURRENT_TIMESTAMP'
3655
- @db.literal(Sequel::CURRENT_TIMESTAMP) == 'CURRENT_TIMESTAMP'
3736
+ @db.literal(Sequel::SQL::Constants::CURRENT_TIMESTAMP).should == 'CURRENT_TIMESTAMP'
3737
+ @db.literal(Sequel::CURRENT_TIMESTAMP).should == 'CURRENT_TIMESTAMP'
3656
3738
  end
3657
3739
 
3658
3740
  it "should have NULL" do
3659
- @db.literal(Sequel::SQL::Constants::NULL) == 'NULL'
3660
- @db.literal(Sequel::NULL) == 'NULL'
3741
+ @db.literal(Sequel::SQL::Constants::NULL).should == 'NULL'
3742
+ @db.literal(Sequel::NULL).should == 'NULL'
3661
3743
  end
3662
3744
 
3663
3745
  it "should have NOTNULL" do
3664
- @db.literal(Sequel::SQL::Constants::NOTNULL) == 'NOT NULL'
3665
- @db.literal(Sequel::NOTNULL) == 'NOT NULL'
3746
+ @db.literal(Sequel::SQL::Constants::NOTNULL).should == 'NOT NULL'
3747
+ @db.literal(Sequel::NOTNULL).should == 'NOT NULL'
3666
3748
  end
3667
3749
 
3668
3750
  it "should have TRUE and SQLTRUE" do
3669
- @db.literal(Sequel::SQL::Constants::TRUE) == '1'
3670
- @db.literal(Sequel::TRUE) == '1'
3671
- @db.literal(Sequel::SQL::Constants::SQLTRUE) == '1'
3672
- @db.literal(Sequel::SQLTRUE) == '1'
3751
+ @db.literal(Sequel::SQL::Constants::TRUE).should == "'t'"
3752
+ @db.literal(Sequel::TRUE).should == "'t'"
3753
+ @db.literal(Sequel::SQL::Constants::SQLTRUE).should == "'t'"
3754
+ @db.literal(Sequel::SQLTRUE).should == "'t'"
3673
3755
  end
3674
3756
 
3675
3757
  it "should have FALSE and SQLFALSE" do
3676
- @db.literal(Sequel::SQL::Constants::FALSE) == '0'
3677
- @db.literal(Sequel::FALSE) == '0'
3678
- @db.literal(Sequel::SQL::Constants::SQLFALSE) == '0'
3679
- @db.literal(Sequel::SQLFALSE) == '0'
3758
+ @db.literal(Sequel::SQL::Constants::FALSE).should == "'f'"
3759
+ @db.literal(Sequel::FALSE).should == "'f'"
3760
+ @db.literal(Sequel::SQL::Constants::SQLFALSE).should == "'f'"
3761
+ @db.literal(Sequel::SQLFALSE).should == "'f'"
3680
3762
  end
3681
3763
  end
3682
3764
 
3683
3765
  describe "Sequel timezone support" do
3684
3766
  before do
3685
- @db = MockDatabase.new
3767
+ @db = Sequel::Database.new
3686
3768
  @dataset = @db.dataset
3687
3769
  @dataset.meta_def(:supports_timestamp_timezones?){true}
3688
3770
  @dataset.meta_def(:supports_timestamp_usecs?){false}
@@ -3717,6 +3799,30 @@ describe "Sequel timezone support" do
3717
3799
  @dataset.literal(t).should == "#{s}#{@offset}'"
3718
3800
  end
3719
3801
 
3802
+ specify "should have Database#timezone override Sequel.database_timezone" do
3803
+ Sequel.database_timezone = :local
3804
+ @db.timezone = :utc
3805
+
3806
+ t = Time.now
3807
+ s = t.getutc.strftime("'%Y-%m-%d %H:%M:%S")
3808
+ @dataset.literal(t).should == "#{s}+0000'"
3809
+
3810
+ t = DateTime.now
3811
+ s = t.new_offset(0).strftime("'%Y-%m-%d %H:%M:%S")
3812
+ @dataset.literal(t).should == "#{s}+0000'"
3813
+
3814
+ Sequel.database_timezone = :utc
3815
+ @db.timezone = :local
3816
+
3817
+ t = Time.now.utc
3818
+ s = t.getlocal.strftime("'%Y-%m-%d %H:%M:%S")
3819
+ @dataset.literal(t).should == "#{s}#{@offset}'"
3820
+
3821
+ t = DateTime.now.new_offset(0)
3822
+ s = t.new_offset(DateTime.now.offset).strftime("'%Y-%m-%d %H:%M:%S")
3823
+ @dataset.literal(t).should == "#{s}#{@offset}'"
3824
+ end
3825
+
3720
3826
  specify "should handle converting database timestamps into application timestamps" do
3721
3827
  Sequel.database_timezone = :utc
3722
3828
  Sequel.application_timezone = :local
@@ -3827,13 +3933,7 @@ end
3827
3933
 
3828
3934
  describe "Sequel::Dataset#select_map" do
3829
3935
  before do
3830
- @ds = MockDatabase.new[:t]
3831
- def @ds.fetch_rows(sql)
3832
- db << sql
3833
- yield({:c=>1})
3834
- yield({:c=>2})
3835
- end
3836
- @ds.db.reset
3936
+ @ds = Sequel.mock(:fetch=>[{:c=>1}, {:c=>2}])[:t]
3837
3937
  end
3838
3938
 
3839
3939
  specify "should do select and map in one step" do
@@ -3846,6 +3946,12 @@ describe "Sequel::Dataset#select_map" do
3846
3946
  @ds.db.sqls.should == ['SELECT a.b FROM t']
3847
3947
  end
3848
3948
 
3949
+ specify "should raise if multiple arguments and can't determine alias" do
3950
+ proc{@ds.select_map([:a.sql_function, :b])}.should raise_error(Sequel::Error)
3951
+ proc{@ds.select_map(:a.sql_function){b}}.should raise_error(Sequel::Error)
3952
+ proc{@ds.select_map{[a{}, b]}}.should raise_error(Sequel::Error)
3953
+ end
3954
+
3849
3955
  specify "should handle implicit aliases in arguments" do
3850
3956
  @ds.select_map(:a___b).should == [1, 2]
3851
3957
  @ds.db.sqls.should == ['SELECT a AS b FROM t']
@@ -3856,21 +3962,47 @@ describe "Sequel::Dataset#select_map" do
3856
3962
  @ds.db.sqls.should == ['SELECT a AS b FROM t']
3857
3963
  end
3858
3964
 
3965
+ specify "should handle identifiers with strings" do
3966
+ @ds.select_map([Sequel::SQL::Identifier.new('c'), :c]).should == [[1, 1], [2, 2]]
3967
+ @ds.db.sqls.should == ['SELECT c, c FROM t']
3968
+ end
3969
+
3859
3970
  specify "should accept a block" do
3860
3971
  @ds.select_map{a(t__c)}.should == [1, 2]
3861
3972
  @ds.db.sqls.should == ['SELECT a(t.c) FROM t']
3862
3973
  end
3974
+
3975
+ specify "should accept a block with an array of columns" do
3976
+ @ds.select_map{[a(t__c).as(c), a(t__c).as(c)]}.should == [[1, 1], [2, 2]]
3977
+ @ds.db.sqls.should == ['SELECT a(t.c) AS c, a(t.c) AS c FROM t']
3978
+ end
3979
+
3980
+ specify "should accept a block with a column" do
3981
+ @ds.select_map(:c){a(t__c).as(c)}.should == [[1, 1], [2, 2]]
3982
+ @ds.db.sqls.should == ['SELECT c, a(t.c) AS c FROM t']
3983
+ end
3984
+
3985
+ specify "should accept a block and array of arguments" do
3986
+ @ds.select_map([:c, :c]){[a(t__c).as(c), a(t__c).as(c)]}.should == [[1, 1, 1, 1], [2, 2, 2, 2]]
3987
+ @ds.db.sqls.should == ['SELECT c, c, a(t.c) AS c, a(t.c) AS c FROM t']
3988
+ end
3989
+
3990
+ specify "should handle an array of columns" do
3991
+ @ds.select_map([:c, :c]).should == [[1, 1], [2, 2]]
3992
+ @ds.db.sqls.should == ['SELECT c, c FROM t']
3993
+ @ds.select_map([:d.as(:c), :c.qualify(:b), :c.identifier, :c.identifier.qualify(:b), :a__c, :a__d___c]).should == [[1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2]]
3994
+ @ds.db.sqls.should == ['SELECT d AS c, b.c, c, b.c, a.c, a.d AS c FROM t']
3995
+ end
3996
+
3997
+ specify "should handle an array with a single element" do
3998
+ @ds.select_map([:c]).should == [[1], [2]]
3999
+ @ds.db.sqls.should == ['SELECT c FROM t']
4000
+ end
3863
4001
  end
3864
4002
 
3865
4003
  describe "Sequel::Dataset#select_order_map" do
3866
4004
  before do
3867
- @ds = MockDatabase.new[:t]
3868
- def @ds.fetch_rows(sql)
3869
- db << sql
3870
- yield({:c=>1})
3871
- yield({:c=>2})
3872
- end
3873
- @ds.db.reset
4005
+ @ds = Sequel.mock(:fetch=>[{:c=>1}, {:c=>2}])[:t]
3874
4006
  end
3875
4007
 
3876
4008
  specify "should do select and map in one step" do
@@ -3883,6 +4015,12 @@ describe "Sequel::Dataset#select_order_map" do
3883
4015
  @ds.db.sqls.should == ['SELECT a.b FROM t ORDER BY a.b']
3884
4016
  end
3885
4017
 
4018
+ specify "should raise if multiple arguments and can't determine alias" do
4019
+ proc{@ds.select_order_map([:a.sql_function, :b])}.should raise_error(Sequel::Error)
4020
+ proc{@ds.select_order_map(:a.sql_function){b}}.should raise_error(Sequel::Error)
4021
+ proc{@ds.select_order_map{[a{}, b]}}.should raise_error(Sequel::Error)
4022
+ end
4023
+
3886
4024
  specify "should handle implicit aliases in arguments" do
3887
4025
  @ds.select_order_map(:a___b).should == [1, 2]
3888
4026
  @ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
@@ -3898,55 +4036,160 @@ describe "Sequel::Dataset#select_order_map" do
3898
4036
  @ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
3899
4037
  end
3900
4038
 
4039
+ specify "should handle OrderedExpressions" do
4040
+ @ds.select_order_map(:a.desc).should == [1, 2]
4041
+ @ds.db.sqls.should == ['SELECT a FROM t ORDER BY a DESC']
4042
+ end
4043
+
3901
4044
  specify "should accept a block" do
3902
4045
  @ds.select_order_map{a(t__c)}.should == [1, 2]
3903
4046
  @ds.db.sqls.should == ['SELECT a(t.c) FROM t ORDER BY a(t.c)']
3904
4047
  end
4048
+
4049
+ specify "should accept a block with an array of columns" do
4050
+ @ds.select_order_map{[c.desc, a(t__c).as(c)]}.should == [[1, 1], [2, 2]]
4051
+ @ds.db.sqls.should == ['SELECT c, a(t.c) AS c FROM t ORDER BY c DESC, a(t.c)']
4052
+ end
4053
+
4054
+ specify "should accept a block with a column" do
4055
+ @ds.select_order_map(:c){a(t__c).as(c)}.should == [[1, 1], [2, 2]]
4056
+ @ds.db.sqls.should == ['SELECT c, a(t.c) AS c FROM t ORDER BY c, a(t.c)']
4057
+ end
4058
+
4059
+ specify "should accept a block and array of arguments" do
4060
+ @ds.select_order_map([:c, :c]){[a(t__c).as(c), c.desc]}.should == [[1, 1, 1, 1], [2, 2, 2, 2]]
4061
+ @ds.db.sqls.should == ['SELECT c, c, a(t.c) AS c, c FROM t ORDER BY c, c, a(t.c), c DESC']
4062
+ end
4063
+
4064
+ specify "should handle an array of columns" do
4065
+ @ds.select_order_map([:c, :c]).should == [[1, 1], [2, 2]]
4066
+ @ds.db.sqls.should == ['SELECT c, c FROM t ORDER BY c, c']
4067
+ @ds.select_order_map([:d.as(:c), :c.qualify(:b), :c.identifier, :c.identifier.qualify(:b), :c.identifier.qualify(:b).desc, :a__c, :a__d___c.desc]).should == [[1, 1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2, 2]]
4068
+ @ds.db.sqls.should == ['SELECT d AS c, b.c, c, b.c, b.c, a.c, a.d AS c FROM t ORDER BY d, b.c, c, b.c, b.c DESC, a.c, a.d DESC']
4069
+ end
4070
+
4071
+ specify "should handle an array with a single element" do
4072
+ @ds.select_order_map([:c]).should == [[1], [2]]
4073
+ @ds.db.sqls.should == ['SELECT c FROM t ORDER BY c']
4074
+ end
3905
4075
  end
3906
4076
 
3907
4077
  describe "Sequel::Dataset#select_hash" do
3908
4078
  before do
3909
- @ds = MockDatabase.new[:t]
3910
- def @ds.set_fr_yield(hs)
3911
- @hs = hs
3912
- end
3913
- def @ds.fetch_rows(sql)
3914
- db << sql
3915
- @hs.each{|h| yield h}
3916
- end
3917
- @ds.db.reset
4079
+ @db = Sequel.mock(:fetch=>[{:a=>1, :b=>2}, {:a=>3, :b=>4}])
4080
+ @ds = @db[:t]
3918
4081
  end
3919
4082
 
3920
- specify "should do select and map in one step" do
3921
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
4083
+ specify "should do select and to_hash in one step" do
3922
4084
  @ds.select_hash(:a, :b).should == {1=>2, 3=>4}
3923
4085
  @ds.db.sqls.should == ['SELECT a, b FROM t']
3924
4086
  end
3925
4087
 
3926
4088
  specify "should handle implicit qualifiers in arguments" do
3927
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3928
4089
  @ds.select_hash(:t__a, :t__b).should == {1=>2, 3=>4}
3929
4090
  @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
3930
4091
  end
3931
4092
 
3932
4093
  specify "should handle implicit aliases in arguments" do
3933
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3934
4094
  @ds.select_hash(:c___a, :d___b).should == {1=>2, 3=>4}
3935
4095
  @ds.db.sqls.should == ['SELECT c AS a, d AS b FROM t']
3936
4096
  end
3937
4097
 
3938
4098
  specify "should handle implicit qualifiers and aliases in arguments" do
3939
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3940
4099
  @ds.select_hash(:t__c___a, :t__d___b).should == {1=>2, 3=>4}
3941
4100
  @ds.db.sqls.should == ['SELECT t.c AS a, t.d AS b FROM t']
3942
4101
  end
4102
+
4103
+ specify "should handle SQL::Identifiers in arguments" do
4104
+ @ds.select_hash(:a.identifier, :b.identifier).should == {1=>2, 3=>4}
4105
+ @ds.db.sqls.should == ['SELECT a, b FROM t']
4106
+ end
4107
+
4108
+ specify "should handle SQL::QualifiedIdentifiers in arguments" do
4109
+ @ds.select_hash(:a.qualify(:t), :b.identifier.qualify(:t)).should == {1=>2, 3=>4}
4110
+ @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
4111
+ end
4112
+
4113
+ specify "should handle SQL::AliasedExpressions in arguments" do
4114
+ @ds.select_hash(:c.as(:a), :t.as(:b)).should == {1=>2, 3=>4}
4115
+ @ds.db.sqls.should == ['SELECT c AS a, t AS b FROM t']
4116
+ end
4117
+
4118
+ specify "should work with arrays of columns" do
4119
+ @db.fetch = [{:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}]
4120
+ @ds.select_hash([:a, :c], :b).should == {[1, 3]=>2, [4, 6]=>5}
4121
+ @ds.db.sqls.should == ['SELECT a, c, b FROM t']
4122
+ @ds.select_hash(:a, [:b, :c]).should == {1=>[2, 3], 4=>[5, 6]}
4123
+ @ds.db.sqls.should == ['SELECT a, b, c FROM t']
4124
+ @ds.select_hash([:a, :b], [:b, :c]).should == {[1, 2]=>[2, 3], [4, 5]=>[5, 6]}
4125
+ @ds.db.sqls.should == ['SELECT a, b, b, c FROM t']
4126
+ end
4127
+
4128
+ specify "should raise an error if the resulting symbol cannot be determined" do
4129
+ proc{@ds.select_hash(:c.as(:a), :b.sql_function)}.should raise_error(Sequel::Error)
4130
+ end
4131
+ end
4132
+
4133
+ describe "Sequel::Dataset#select_hash_groups" do
4134
+ before do
4135
+ @db = Sequel.mock(:fetch=>[{:a=>1, :b=>2}, {:a=>3, :b=>4}])
4136
+ @ds = @db[:t]
4137
+ end
4138
+
4139
+ specify "should do select and to_hash in one step" do
4140
+ @ds.select_hash_groups(:a, :b).should == {1=>[2], 3=>[4]}
4141
+ @ds.db.sqls.should == ['SELECT a, b FROM t']
4142
+ end
4143
+
4144
+ specify "should handle implicit qualifiers in arguments" do
4145
+ @ds.select_hash_groups(:t__a, :t__b).should == {1=>[2], 3=>[4]}
4146
+ @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
4147
+ end
4148
+
4149
+ specify "should handle implicit aliases in arguments" do
4150
+ @ds.select_hash_groups(:c___a, :d___b).should == {1=>[2], 3=>[4]}
4151
+ @ds.db.sqls.should == ['SELECT c AS a, d AS b FROM t']
4152
+ end
4153
+
4154
+ specify "should handle implicit qualifiers and aliases in arguments" do
4155
+ @ds.select_hash_groups(:t__c___a, :t__d___b).should == {1=>[2], 3=>[4]}
4156
+ @ds.db.sqls.should == ['SELECT t.c AS a, t.d AS b FROM t']
4157
+ end
4158
+
4159
+ specify "should handle SQL::Identifiers in arguments" do
4160
+ @ds.select_hash_groups(:a.identifier, :b.identifier).should == {1=>[2], 3=>[4]}
4161
+ @ds.db.sqls.should == ['SELECT a, b FROM t']
4162
+ end
4163
+
4164
+ specify "should handle SQL::QualifiedIdentifiers in arguments" do
4165
+ @ds.select_hash_groups(:a.qualify(:t), :b.identifier.qualify(:t)).should == {1=>[2], 3=>[4]}
4166
+ @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
4167
+ end
4168
+
4169
+ specify "should handle SQL::AliasedExpressions in arguments" do
4170
+ @ds.select_hash_groups(:c.as(:a), :t.as(:b)).should == {1=>[2], 3=>[4]}
4171
+ @ds.db.sqls.should == ['SELECT c AS a, t AS b FROM t']
4172
+ end
4173
+
4174
+ specify "should work with arrays of columns" do
4175
+ @db.fetch = [{:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}]
4176
+ @ds.select_hash_groups([:a, :c], :b).should == {[1, 3]=>[2], [4, 6]=>[5]}
4177
+ @ds.db.sqls.should == ['SELECT a, c, b FROM t']
4178
+ @ds.select_hash_groups(:a, [:b, :c]).should == {1=>[[2, 3]], 4=>[[5, 6]]}
4179
+ @ds.db.sqls.should == ['SELECT a, b, c FROM t']
4180
+ @ds.select_hash_groups([:a, :b], [:b, :c]).should == {[1, 2]=>[[2, 3]], [4, 5]=>[[5, 6]]}
4181
+ @ds.db.sqls.should == ['SELECT a, b, b, c FROM t']
4182
+ end
4183
+
4184
+ specify "should raise an error if the resulting symbol cannot be determined" do
4185
+ proc{@ds.select_hash_groups(:c.as(:a), :b.sql_function)}.should raise_error(Sequel::Error)
4186
+ end
3943
4187
  end
3944
4188
 
3945
4189
  describe "Modifying joined datasets" do
3946
4190
  before do
3947
- @ds = MockDatabase.new.from(:b, :c).join(:d, [:id]).where(:id => 2)
4191
+ @ds = Sequel.mock.from(:b, :c).join(:d, [:id]).where(:id => 2)
3948
4192
  @ds.meta_def(:supports_modifying_joins?){true}
3949
- @ds.db.reset
3950
4193
  end
3951
4194
 
3952
4195
  specify "should allow deleting from joined datasets" do
@@ -3962,7 +4205,7 @@ end
3962
4205
 
3963
4206
  describe "Dataset#lock_style and for_update" do
3964
4207
  before do
3965
- @ds = MockDatabase.new[:t]
4208
+ @ds = Sequel::Dataset.new(nil).from(:t)
3966
4209
  end
3967
4210
 
3968
4211
  specify "#for_update should use FOR UPDATE" do
@@ -3977,3 +4220,89 @@ describe "Dataset#lock_style and for_update" do
3977
4220
  @ds.lock_style("FOR SHARE").sql.should == "SELECT * FROM t FOR SHARE"
3978
4221
  end
3979
4222
  end
4223
+
4224
+ describe "Custom ASTTransformer" do
4225
+ specify "should transform given objects" do
4226
+ c = Class.new(Sequel::ASTTransformer) do
4227
+ def v(s)
4228
+ (s.is_a?(Symbol) || s.is_a?(String)) ? :"#{s}#{s}" : super
4229
+ end
4230
+ end.new
4231
+ ds = Sequel::Dataset.new(nil).from(:t).cross_join(:a___g).join(:b___h, [:c]).join(:d___i, :e=>:f)
4232
+ ds.sql.should == 'SELECT * FROM t CROSS JOIN a AS g INNER JOIN b AS h USING (c) INNER JOIN d AS i ON (i.e = h.f)'
4233
+ ds.clone(:from=>c.transform(ds.opts[:from]), :join=>c.transform(ds.opts[:join])).sql.should == 'SELECT * FROM tt CROSS JOIN aa AS gg INNER JOIN bb AS hh USING (cc) INNER JOIN dd AS ii ON (ii.ee = hh.ff)'
4234
+ end
4235
+ end
4236
+
4237
+ describe "Dataset#returning" do
4238
+ before do
4239
+ @ds = Sequel.mock(:fetch=>proc{|s| {:foo=>s}})[:t].returning(:foo)
4240
+ @pr = proc do
4241
+ [:insert, :update, :delete].each do |m|
4242
+ @ds.meta_def(:"#{m}_clause_methods"){super() + [:"#{m}_returning_sql"]}
4243
+ end
4244
+ end
4245
+ end
4246
+
4247
+ specify "should use RETURNING clause in the SQL if the dataset supports it" do
4248
+ @pr.call
4249
+ @ds.delete_sql.should == "DELETE FROM t RETURNING foo"
4250
+ @ds.insert_sql(1).should == "INSERT INTO t VALUES (1) RETURNING foo"
4251
+ @ds.update_sql(:foo=>1).should == "UPDATE t SET foo = 1 RETURNING foo"
4252
+ end
4253
+
4254
+ specify "should not use RETURNING clause in the SQL if the dataset does not support it" do
4255
+ @ds.delete_sql.should == "DELETE FROM t"
4256
+ @ds.insert_sql(1).should == "INSERT INTO t VALUES (1)"
4257
+ @ds.update_sql(:foo=>1).should == "UPDATE t SET foo = 1"
4258
+ end
4259
+
4260
+ specify "should have insert, update, and delete yield to blocks if RETURNING is used" do
4261
+ @pr.call
4262
+ h = {}
4263
+ @ds.delete{|r| h = r}
4264
+ h.should == {:foo=>"DELETE FROM t RETURNING foo"}
4265
+ @ds.insert(1){|r| h = r}
4266
+ h.should == {:foo=>"INSERT INTO t VALUES (1) RETURNING foo"}
4267
+ @ds.update(:foo=>1){|r| h = r}
4268
+ h.should == {:foo=>"UPDATE t SET foo = 1 RETURNING foo"}
4269
+ end
4270
+
4271
+ specify "should have insert, update, and delete return arrays of hashes if RETURNING is used and a block is not given" do
4272
+ @pr.call
4273
+ h = {}
4274
+ @ds.delete.should == [{:foo=>"DELETE FROM t RETURNING foo"}]
4275
+ @ds.insert(1).should == [{:foo=>"INSERT INTO t VALUES (1) RETURNING foo"}]
4276
+ @ds.update(:foo=>1).should == [{:foo=>"UPDATE t SET foo = 1 RETURNING foo"}]
4277
+ end
4278
+ end
4279
+
4280
+ describe "Dataset emulating bitwise operator support" do
4281
+ before do
4282
+ @ds = Sequel::Database.new.dataset
4283
+ @ds.quote_identifiers = true
4284
+ def @ds.complex_expression_sql_append(sql, op, args)
4285
+ sql << complex_expression_arg_pairs(args){|a, b| "bitand(#{literal(a)}, #{literal(b)})"}
4286
+ end
4287
+ end
4288
+
4289
+ it "should work with any numbers of arguments for operators" do
4290
+ @ds.select(Sequel::SQL::ComplexExpression.new(:&, :x)).sql.should == 'SELECT "x"'
4291
+ @ds.select(:x & 1).sql.should == 'SELECT bitand("x", 1)'
4292
+ @ds.select(:x & 1 & 2).sql.should == 'SELECT bitand(bitand("x", 1), 2)'
4293
+ end
4294
+ end
4295
+
4296
+ describe "Dataset feature defaults" do
4297
+ it "should not require aliases for recursive CTEs by default" do
4298
+ Sequel::Database.new.dataset.recursive_cte_requires_column_aliases?.should be_false
4299
+ end
4300
+
4301
+ it "should not require placeholder type specifiers by default" do
4302
+ Sequel::Database.new.dataset.requires_placeholder_type_specifiers?.should be_false
4303
+ end
4304
+
4305
+ it "offset use should be returning a separate row number column by default" do
4306
+ Sequel::Database.new.dataset.send(:offset_returns_row_number_column?).should be_false
4307
+ end
4308
+ end