|
1 <?php |
|
2 |
|
3 /** |
|
4 * Dummy class for fetching into a class. |
|
5 * |
|
6 * PDO supports using a new instance of an arbitrary class for records |
|
7 * rather than just a stdClass or array. This class is for testing that |
|
8 * functionality. (See testQueryFetchClass() below) |
|
9 */ |
|
10 class FakeRecord { } |
|
11 |
|
12 /** |
|
13 * Base test class for databases. |
|
14 * |
|
15 * Because all database tests share the same test data, we can centralize that |
|
16 * here. |
|
17 */ |
|
18 class DatabaseTestCase extends DrupalWebTestCase { |
|
19 protected $profile = 'testing'; |
|
20 |
|
21 function setUp() { |
|
22 parent::setUp('database_test'); |
|
23 |
|
24 $schema['test'] = drupal_get_schema('test'); |
|
25 $schema['test_people'] = drupal_get_schema('test_people'); |
|
26 $schema['test_people_copy'] = drupal_get_schema('test_people_copy'); |
|
27 $schema['test_one_blob'] = drupal_get_schema('test_one_blob'); |
|
28 $schema['test_two_blobs'] = drupal_get_schema('test_two_blobs'); |
|
29 $schema['test_task'] = drupal_get_schema('test_task'); |
|
30 |
|
31 $this->installTables($schema); |
|
32 |
|
33 $this->addSampleData(); |
|
34 } |
|
35 |
|
36 /** |
|
37 * Set up several tables needed by a certain test. |
|
38 * |
|
39 * @param $schema |
|
40 * An array of table definitions to install. |
|
41 */ |
|
42 function installTables($schema) { |
|
43 // This ends up being a test for table drop and create, too, which is nice. |
|
44 foreach ($schema as $name => $data) { |
|
45 if (db_table_exists($name)) { |
|
46 db_drop_table($name); |
|
47 } |
|
48 db_create_table($name, $data); |
|
49 } |
|
50 |
|
51 foreach ($schema as $name => $data) { |
|
52 $this->assertTrue(db_table_exists($name), format_string('Table @name created successfully.', array('@name' => $name))); |
|
53 } |
|
54 } |
|
55 |
|
56 /** |
|
57 * Set up tables for NULL handling. |
|
58 */ |
|
59 function ensureSampleDataNull() { |
|
60 $schema['test_null'] = drupal_get_schema('test_null'); |
|
61 $this->installTables($schema); |
|
62 |
|
63 db_insert('test_null') |
|
64 ->fields(array('name', 'age')) |
|
65 ->values(array( |
|
66 'name' => 'Kermit', |
|
67 'age' => 25, |
|
68 )) |
|
69 ->values(array( |
|
70 'name' => 'Fozzie', |
|
71 'age' => NULL, |
|
72 )) |
|
73 ->values(array( |
|
74 'name' => 'Gonzo', |
|
75 'age' => 27, |
|
76 )) |
|
77 ->execute(); |
|
78 } |
|
79 |
|
80 /** |
|
81 * Setup our sample data. |
|
82 * |
|
83 * These are added using db_query(), since we're not trying to test the |
|
84 * INSERT operations here, just populate. |
|
85 */ |
|
86 function addSampleData() { |
|
87 // We need the IDs, so we can't use a multi-insert here. |
|
88 $john = db_insert('test') |
|
89 ->fields(array( |
|
90 'name' => 'John', |
|
91 'age' => 25, |
|
92 'job' => 'Singer', |
|
93 )) |
|
94 ->execute(); |
|
95 |
|
96 $george = db_insert('test') |
|
97 ->fields(array( |
|
98 'name' => 'George', |
|
99 'age' => 27, |
|
100 'job' => 'Singer', |
|
101 )) |
|
102 ->execute(); |
|
103 |
|
104 $ringo = db_insert('test') |
|
105 ->fields(array( |
|
106 'name' => 'Ringo', |
|
107 'age' => 28, |
|
108 'job' => 'Drummer', |
|
109 )) |
|
110 ->execute(); |
|
111 |
|
112 $paul = db_insert('test') |
|
113 ->fields(array( |
|
114 'name' => 'Paul', |
|
115 'age' => 26, |
|
116 'job' => 'Songwriter', |
|
117 )) |
|
118 ->execute(); |
|
119 |
|
120 db_insert('test_people') |
|
121 ->fields(array( |
|
122 'name' => 'Meredith', |
|
123 'age' => 30, |
|
124 'job' => 'Speaker', |
|
125 )) |
|
126 ->execute(); |
|
127 |
|
128 db_insert('test_task') |
|
129 ->fields(array('pid', 'task', 'priority')) |
|
130 ->values(array( |
|
131 'pid' => $john, |
|
132 'task' => 'eat', |
|
133 'priority' => 3, |
|
134 )) |
|
135 ->values(array( |
|
136 'pid' => $john, |
|
137 'task' => 'sleep', |
|
138 'priority' => 4, |
|
139 )) |
|
140 ->values(array( |
|
141 'pid' => $john, |
|
142 'task' => 'code', |
|
143 'priority' => 1, |
|
144 )) |
|
145 ->values(array( |
|
146 'pid' => $george, |
|
147 'task' => 'sing', |
|
148 'priority' => 2, |
|
149 )) |
|
150 ->values(array( |
|
151 'pid' => $george, |
|
152 'task' => 'sleep', |
|
153 'priority' => 2, |
|
154 )) |
|
155 ->values(array( |
|
156 'pid' => $paul, |
|
157 'task' => 'found new band', |
|
158 'priority' => 1, |
|
159 )) |
|
160 ->values(array( |
|
161 'pid' => $paul, |
|
162 'task' => 'perform at superbowl', |
|
163 'priority' => 3, |
|
164 )) |
|
165 ->execute(); |
|
166 } |
|
167 } |
|
168 |
|
169 /** |
|
170 * Test connection management. |
|
171 */ |
|
172 class DatabaseConnectionTestCase extends DatabaseTestCase { |
|
173 |
|
174 public static function getInfo() { |
|
175 return array( |
|
176 'name' => 'Connection tests', |
|
177 'description' => 'Tests of the core database system.', |
|
178 'group' => 'Database', |
|
179 ); |
|
180 } |
|
181 |
|
182 /** |
|
183 * Test that connections return appropriate connection objects. |
|
184 */ |
|
185 function testConnectionRouting() { |
|
186 // Clone the master credentials to a slave connection. |
|
187 // Note this will result in two independent connection objects that happen |
|
188 // to point to the same place. |
|
189 $connection_info = Database::getConnectionInfo('default'); |
|
190 Database::addConnectionInfo('default', 'slave', $connection_info['default']); |
|
191 |
|
192 $db1 = Database::getConnection('default', 'default'); |
|
193 $db2 = Database::getConnection('slave', 'default'); |
|
194 |
|
195 $this->assertNotNull($db1, 'default connection is a real connection object.'); |
|
196 $this->assertNotNull($db2, 'slave connection is a real connection object.'); |
|
197 $this->assertNotIdentical($db1, $db2, 'Each target refers to a different connection.'); |
|
198 |
|
199 // Try to open those targets another time, that should return the same objects. |
|
200 $db1b = Database::getConnection('default', 'default'); |
|
201 $db2b = Database::getConnection('slave', 'default'); |
|
202 $this->assertIdentical($db1, $db1b, 'A second call to getConnection() returns the same object.'); |
|
203 $this->assertIdentical($db2, $db2b, 'A second call to getConnection() returns the same object.'); |
|
204 |
|
205 // Try to open an unknown target. |
|
206 $unknown_target = $this->randomName(); |
|
207 $db3 = Database::getConnection($unknown_target, 'default'); |
|
208 $this->assertNotNull($db3, 'Opening an unknown target returns a real connection object.'); |
|
209 $this->assertIdentical($db1, $db3, 'An unknown target opens the default connection.'); |
|
210 |
|
211 // Try to open that unknown target another time, that should return the same object. |
|
212 $db3b = Database::getConnection($unknown_target, 'default'); |
|
213 $this->assertIdentical($db3, $db3b, 'A second call to getConnection() returns the same object.'); |
|
214 } |
|
215 |
|
216 /** |
|
217 * Test that connections return appropriate connection objects. |
|
218 */ |
|
219 function testConnectionRoutingOverride() { |
|
220 // Clone the master credentials to a slave connection. |
|
221 // Note this will result in two independent connection objects that happen |
|
222 // to point to the same place. |
|
223 $connection_info = Database::getConnectionInfo('default'); |
|
224 Database::addConnectionInfo('default', 'slave', $connection_info['default']); |
|
225 |
|
226 Database::ignoreTarget('default', 'slave'); |
|
227 |
|
228 $db1 = Database::getConnection('default', 'default'); |
|
229 $db2 = Database::getConnection('slave', 'default'); |
|
230 |
|
231 $this->assertIdentical($db1, $db2, 'Both targets refer to the same connection.'); |
|
232 } |
|
233 |
|
234 /** |
|
235 * Tests the closing of a database connection. |
|
236 */ |
|
237 function testConnectionClosing() { |
|
238 // Open the default target so we have an object to compare. |
|
239 $db1 = Database::getConnection('default', 'default'); |
|
240 |
|
241 // Try to close the default connection, then open a new one. |
|
242 Database::closeConnection('default', 'default'); |
|
243 $db2 = Database::getConnection('default', 'default'); |
|
244 |
|
245 // Opening a connection after closing it should yield an object different than the original. |
|
246 $this->assertNotIdentical($db1, $db2, 'Opening the default connection after it is closed returns a new object.'); |
|
247 } |
|
248 |
|
249 /** |
|
250 * Tests the connection options of the active database. |
|
251 */ |
|
252 function testConnectionOptions() { |
|
253 $connection_info = Database::getConnectionInfo('default'); |
|
254 |
|
255 // Be sure we're connected to the default database. |
|
256 $db = Database::getConnection('default', 'default'); |
|
257 $connectionOptions = $db->getConnectionOptions(); |
|
258 |
|
259 // In the MySQL driver, the port can be different, so check individual |
|
260 // options. |
|
261 $this->assertEqual($connection_info['default']['driver'], $connectionOptions['driver'], 'The default connection info driver matches the current connection options driver.'); |
|
262 $this->assertEqual($connection_info['default']['database'], $connectionOptions['database'], 'The default connection info database matches the current connection options database.'); |
|
263 |
|
264 // Set up identical slave and confirm connection options are identical. |
|
265 Database::addConnectionInfo('default', 'slave', $connection_info['default']); |
|
266 $db2 = Database::getConnection('slave', 'default'); |
|
267 $connectionOptions2 = $db2->getConnectionOptions(); |
|
268 |
|
269 // Get a fresh copy of the default connection options. |
|
270 $connectionOptions = $db->getConnectionOptions(); |
|
271 $this->assertIdentical($connectionOptions, $connectionOptions2, 'The default and slave connection options are identical.'); |
|
272 |
|
273 // Set up a new connection with different connection info. |
|
274 $test = $connection_info['default']; |
|
275 $test['database'] .= 'test'; |
|
276 Database::addConnectionInfo('test', 'default', $test); |
|
277 $connection_info = Database::getConnectionInfo('test'); |
|
278 |
|
279 // Get a fresh copy of the default connection options. |
|
280 $connectionOptions = $db->getConnectionOptions(); |
|
281 $this->assertNotEqual($connection_info['default']['database'], $connectionOptions['database'], 'The test connection info database does not match the current connection options database.'); |
|
282 } |
|
283 } |
|
284 |
|
285 /** |
|
286 * Test cloning Select queries. |
|
287 */ |
|
288 class DatabaseSelectCloneTest extends DatabaseTestCase { |
|
289 |
|
290 public static function getInfo() { |
|
291 return array( |
|
292 'name' => 'Select tests, cloning', |
|
293 'description' => 'Test cloning Select queries.', |
|
294 'group' => 'Database', |
|
295 ); |
|
296 } |
|
297 |
|
298 /** |
|
299 * Test that subqueries as value within conditions are cloned properly. |
|
300 */ |
|
301 function testSelectConditionSubQueryCloning() { |
|
302 $subquery = db_select('test', 't'); |
|
303 $subquery->addField('t', 'id', 'id'); |
|
304 $subquery->condition('age', 28, '<'); |
|
305 |
|
306 $query = db_select('test', 't'); |
|
307 $query->addField('t', 'name', 'name'); |
|
308 $query->condition('id', $subquery, 'IN'); |
|
309 |
|
310 $clone = clone $query; |
|
311 // Cloned query should not be altered by the following modification |
|
312 // happening on original query. |
|
313 $subquery->condition('age', 25, '>'); |
|
314 |
|
315 $clone_result = $clone->countQuery()->execute()->fetchField(); |
|
316 $query_result = $query->countQuery()->execute()->fetchField(); |
|
317 |
|
318 // Make sure the cloned query has not been modified |
|
319 $this->assertEqual(3, $clone_result, 'The cloned query returns the expected number of rows'); |
|
320 $this->assertEqual(2, $query_result, 'The query returns the expected number of rows'); |
|
321 } |
|
322 } |
|
323 |
|
324 /** |
|
325 * Test fetch actions, part 1. |
|
326 * |
|
327 * We get timeout errors if we try to run too many tests at once. |
|
328 */ |
|
329 class DatabaseFetchTestCase extends DatabaseTestCase { |
|
330 |
|
331 public static function getInfo() { |
|
332 return array( |
|
333 'name' => 'Fetch tests', |
|
334 'description' => 'Test the Database system\'s various fetch capabilities.', |
|
335 'group' => 'Database', |
|
336 ); |
|
337 } |
|
338 |
|
339 /** |
|
340 * Confirm that we can fetch a record properly in default object mode. |
|
341 */ |
|
342 function testQueryFetchDefault() { |
|
343 $records = array(); |
|
344 $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25)); |
|
345 $this->assertTrue($result instanceof DatabaseStatementInterface, 'Result set is a Drupal statement object.'); |
|
346 foreach ($result as $record) { |
|
347 $records[] = $record; |
|
348 $this->assertTrue(is_object($record), 'Record is an object.'); |
|
349 $this->assertIdentical($record->name, 'John', '25 year old is John.'); |
|
350 } |
|
351 |
|
352 $this->assertIdentical(count($records), 1, 'There is only one record.'); |
|
353 } |
|
354 |
|
355 /** |
|
356 * Confirm that we can fetch a record to an object explicitly. |
|
357 */ |
|
358 function testQueryFetchObject() { |
|
359 $records = array(); |
|
360 $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_OBJ)); |
|
361 foreach ($result as $record) { |
|
362 $records[] = $record; |
|
363 $this->assertTrue(is_object($record), 'Record is an object.'); |
|
364 $this->assertIdentical($record->name, 'John', '25 year old is John.'); |
|
365 } |
|
366 |
|
367 $this->assertIdentical(count($records), 1, 'There is only one record.'); |
|
368 } |
|
369 |
|
370 /** |
|
371 * Confirm that we can fetch a record to an array associative explicitly. |
|
372 */ |
|
373 function testQueryFetchArray() { |
|
374 $records = array(); |
|
375 $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_ASSOC)); |
|
376 foreach ($result as $record) { |
|
377 $records[] = $record; |
|
378 if ($this->assertTrue(is_array($record), 'Record is an array.')) { |
|
379 $this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.'); |
|
380 } |
|
381 } |
|
382 |
|
383 $this->assertIdentical(count($records), 1, 'There is only one record.'); |
|
384 } |
|
385 |
|
386 /** |
|
387 * Confirm that we can fetch a record into a new instance of a custom class. |
|
388 * |
|
389 * @see FakeRecord |
|
390 */ |
|
391 function testQueryFetchClass() { |
|
392 $records = array(); |
|
393 $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => 'FakeRecord')); |
|
394 foreach ($result as $record) { |
|
395 $records[] = $record; |
|
396 if ($this->assertTrue($record instanceof FakeRecord, 'Record is an object of class FakeRecord.')) { |
|
397 $this->assertIdentical($record->name, 'John', '25 year old is John.'); |
|
398 } |
|
399 } |
|
400 |
|
401 $this->assertIdentical(count($records), 1, 'There is only one record.'); |
|
402 } |
|
403 } |
|
404 |
|
405 /** |
|
406 * Test fetch actions, part 2. |
|
407 * |
|
408 * We get timeout errors if we try to run too many tests at once. |
|
409 */ |
|
410 class DatabaseFetch2TestCase extends DatabaseTestCase { |
|
411 |
|
412 public static function getInfo() { |
|
413 return array( |
|
414 'name' => 'Fetch tests, part 2', |
|
415 'description' => 'Test the Database system\'s various fetch capabilities.', |
|
416 'group' => 'Database', |
|
417 ); |
|
418 } |
|
419 |
|
420 function setUp() { |
|
421 parent::setUp(); |
|
422 } |
|
423 |
|
424 // Confirm that we can fetch a record into an indexed array explicitly. |
|
425 function testQueryFetchNum() { |
|
426 $records = array(); |
|
427 $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_NUM)); |
|
428 foreach ($result as $record) { |
|
429 $records[] = $record; |
|
430 if ($this->assertTrue(is_array($record), 'Record is an array.')) { |
|
431 $this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.'); |
|
432 } |
|
433 } |
|
434 |
|
435 $this->assertIdentical(count($records), 1, 'There is only one record'); |
|
436 } |
|
437 |
|
438 /** |
|
439 * Confirm that we can fetch a record into a doubly-keyed array explicitly. |
|
440 */ |
|
441 function testQueryFetchBoth() { |
|
442 $records = array(); |
|
443 $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_BOTH)); |
|
444 foreach ($result as $record) { |
|
445 $records[] = $record; |
|
446 if ($this->assertTrue(is_array($record), 'Record is an array.')) { |
|
447 $this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.'); |
|
448 $this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.'); |
|
449 } |
|
450 } |
|
451 |
|
452 $this->assertIdentical(count($records), 1, 'There is only one record.'); |
|
453 } |
|
454 |
|
455 /** |
|
456 * Confirm that we can fetch an entire column of a result set at once. |
|
457 */ |
|
458 function testQueryFetchCol() { |
|
459 $records = array(); |
|
460 $result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25)); |
|
461 $column = $result->fetchCol(); |
|
462 $this->assertIdentical(count($column), 3, 'fetchCol() returns the right number of records.'); |
|
463 |
|
464 $result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25)); |
|
465 $i = 0; |
|
466 foreach ($result as $record) { |
|
467 $this->assertIdentical($record->name, $column[$i++], 'Column matches direct accesss.'); |
|
468 } |
|
469 } |
|
470 } |
|
471 |
|
472 /** |
|
473 * Test the insert builder. |
|
474 */ |
|
475 class DatabaseInsertTestCase extends DatabaseTestCase { |
|
476 |
|
477 public static function getInfo() { |
|
478 return array( |
|
479 'name' => 'Insert tests', |
|
480 'description' => 'Test the Insert query builder.', |
|
481 'group' => 'Database', |
|
482 ); |
|
483 } |
|
484 |
|
485 /** |
|
486 * Test the very basic insert functionality. |
|
487 */ |
|
488 function testSimpleInsert() { |
|
489 $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
490 |
|
491 $query = db_insert('test'); |
|
492 $query->fields(array( |
|
493 'name' => 'Yoko', |
|
494 'age' => '29', |
|
495 )); |
|
496 $query->execute(); |
|
497 |
|
498 $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
499 $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.'); |
|
500 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Yoko'))->fetchField(); |
|
501 $this->assertIdentical($saved_age, '29', 'Can retrieve after inserting.'); |
|
502 } |
|
503 |
|
504 /** |
|
505 * Test that we can insert multiple records in one query object. |
|
506 */ |
|
507 function testMultiInsert() { |
|
508 $num_records_before = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
509 |
|
510 $query = db_insert('test'); |
|
511 $query->fields(array( |
|
512 'name' => 'Larry', |
|
513 'age' => '30', |
|
514 )); |
|
515 |
|
516 // We should be able to specify values in any order if named. |
|
517 $query->values(array( |
|
518 'age' => '31', |
|
519 'name' => 'Curly', |
|
520 )); |
|
521 |
|
522 // We should be able to say "use the field order". |
|
523 // This is not the recommended mechanism for most cases, but it should work. |
|
524 $query->values(array('Moe', '32')); |
|
525 $query->execute(); |
|
526 |
|
527 $num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
528 $this->assertIdentical($num_records_before + 3, $num_records_after, 'Record inserts correctly.'); |
|
529 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField(); |
|
530 $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); |
|
531 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField(); |
|
532 $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.'); |
|
533 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField(); |
|
534 $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.'); |
|
535 } |
|
536 |
|
537 /** |
|
538 * Test that an insert object can be reused with new data after it executes. |
|
539 */ |
|
540 function testRepeatedInsert() { |
|
541 $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
542 |
|
543 $query = db_insert('test'); |
|
544 |
|
545 $query->fields(array( |
|
546 'name' => 'Larry', |
|
547 'age' => '30', |
|
548 )); |
|
549 $query->execute(); // This should run the insert, but leave the fields intact. |
|
550 |
|
551 // We should be able to specify values in any order if named. |
|
552 $query->values(array( |
|
553 'age' => '31', |
|
554 'name' => 'Curly', |
|
555 )); |
|
556 $query->execute(); |
|
557 |
|
558 // We should be able to say "use the field order". |
|
559 $query->values(array('Moe', '32')); |
|
560 $query->execute(); |
|
561 |
|
562 $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
563 $this->assertIdentical((int) $num_records_before + 3, (int) $num_records_after, 'Record inserts correctly.'); |
|
564 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField(); |
|
565 $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); |
|
566 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField(); |
|
567 $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.'); |
|
568 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField(); |
|
569 $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.'); |
|
570 } |
|
571 |
|
572 /** |
|
573 * Test that we can specify fields without values and specify values later. |
|
574 */ |
|
575 function testInsertFieldOnlyDefinintion() { |
|
576 // This is useful for importers, when we want to create a query and define |
|
577 // its fields once, then loop over a multi-insert execution. |
|
578 db_insert('test') |
|
579 ->fields(array('name', 'age')) |
|
580 ->values(array('Larry', '30')) |
|
581 ->values(array('Curly', '31')) |
|
582 ->values(array('Moe', '32')) |
|
583 ->execute(); |
|
584 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField(); |
|
585 $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); |
|
586 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField(); |
|
587 $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.'); |
|
588 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField(); |
|
589 $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.'); |
|
590 } |
|
591 |
|
592 /** |
|
593 * Test that inserts return the proper auto-increment ID. |
|
594 */ |
|
595 function testInsertLastInsertID() { |
|
596 $id = db_insert('test') |
|
597 ->fields(array( |
|
598 'name' => 'Larry', |
|
599 'age' => '30', |
|
600 )) |
|
601 ->execute(); |
|
602 |
|
603 $this->assertIdentical($id, '5', 'Auto-increment ID returned successfully.'); |
|
604 } |
|
605 |
|
606 /** |
|
607 * Test that the INSERT INTO ... SELECT (fields) ... syntax works. |
|
608 */ |
|
609 function testInsertSelectFields() { |
|
610 $query = db_select('test_people', 'tp'); |
|
611 // The query builder will always append expressions after fields. |
|
612 // Add the expression first to test that the insert fields are correctly |
|
613 // re-ordered. |
|
614 $query->addExpression('tp.age', 'age'); |
|
615 $query |
|
616 ->fields('tp', array('name','job')) |
|
617 ->condition('tp.name', 'Meredith'); |
|
618 |
|
619 // The resulting query should be equivalent to: |
|
620 // INSERT INTO test (age, name, job) |
|
621 // SELECT tp.age AS age, tp.name AS name, tp.job AS job |
|
622 // FROM test_people tp |
|
623 // WHERE tp.name = 'Meredith' |
|
624 db_insert('test') |
|
625 ->from($query) |
|
626 ->execute(); |
|
627 |
|
628 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Meredith'))->fetchField(); |
|
629 $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); |
|
630 } |
|
631 |
|
632 /** |
|
633 * Tests that the INSERT INTO ... SELECT * ... syntax works. |
|
634 */ |
|
635 function testInsertSelectAll() { |
|
636 $query = db_select('test_people', 'tp') |
|
637 ->fields('tp') |
|
638 ->condition('tp.name', 'Meredith'); |
|
639 |
|
640 // The resulting query should be equivalent to: |
|
641 // INSERT INTO test_people_copy |
|
642 // SELECT * |
|
643 // FROM test_people tp |
|
644 // WHERE tp.name = 'Meredith' |
|
645 db_insert('test_people_copy') |
|
646 ->from($query) |
|
647 ->execute(); |
|
648 |
|
649 $saved_age = db_query('SELECT age FROM {test_people_copy} WHERE name = :name', array(':name' => 'Meredith'))->fetchField(); |
|
650 $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); |
|
651 } |
|
652 } |
|
653 |
|
654 /** |
|
655 * Insert tests using LOB fields, which are weird on some databases. |
|
656 */ |
|
657 class DatabaseInsertLOBTestCase extends DatabaseTestCase { |
|
658 |
|
659 public static function getInfo() { |
|
660 return array( |
|
661 'name' => 'Insert tests, LOB fields', |
|
662 'description' => 'Test the Insert query builder with LOB fields.', |
|
663 'group' => 'Database', |
|
664 ); |
|
665 } |
|
666 |
|
667 /** |
|
668 * Test that we can insert a single blob field successfully. |
|
669 */ |
|
670 function testInsertOneBlob() { |
|
671 $data = "This is\000a test."; |
|
672 $this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.'); |
|
673 $id = db_insert('test_one_blob') |
|
674 ->fields(array('blob1' => $data)) |
|
675 ->execute(); |
|
676 $r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc(); |
|
677 $this->assertTrue($r['blob1'] === $data, format_string('Can insert a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r)))); |
|
678 } |
|
679 |
|
680 /** |
|
681 * Test that we can insert multiple blob fields in the same query. |
|
682 */ |
|
683 function testInsertMultipleBlob() { |
|
684 $id = db_insert('test_two_blobs') |
|
685 ->fields(array( |
|
686 'blob1' => 'This is', |
|
687 'blob2' => 'a test', |
|
688 )) |
|
689 ->execute(); |
|
690 $r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc(); |
|
691 $this->assertTrue($r['blob1'] === 'This is' && $r['blob2'] === 'a test', 'Can insert multiple blobs per row.'); |
|
692 } |
|
693 } |
|
694 |
|
695 /** |
|
696 * Insert tests for "database default" values. |
|
697 */ |
|
698 class DatabaseInsertDefaultsTestCase extends DatabaseTestCase { |
|
699 |
|
700 public static function getInfo() { |
|
701 return array( |
|
702 'name' => 'Insert tests, default fields', |
|
703 'description' => 'Test the Insert query builder with default values.', |
|
704 'group' => 'Database', |
|
705 ); |
|
706 } |
|
707 |
|
708 /** |
|
709 * Test that we can run a query that is "default values for everything". |
|
710 */ |
|
711 function testDefaultInsert() { |
|
712 $query = db_insert('test')->useDefaults(array('job')); |
|
713 $id = $query->execute(); |
|
714 |
|
715 $schema = drupal_get_schema('test'); |
|
716 |
|
717 $job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField(); |
|
718 $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.'); |
|
719 } |
|
720 |
|
721 /** |
|
722 * Test that no action will be preformed if no fields are specified. |
|
723 */ |
|
724 function testDefaultEmptyInsert() { |
|
725 $num_records_before = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
726 |
|
727 try { |
|
728 $result = db_insert('test')->execute(); |
|
729 // This is only executed if no exception has been thrown. |
|
730 $this->fail('Expected exception NoFieldsException has not been thrown.'); |
|
731 } catch (NoFieldsException $e) { |
|
732 $this->pass('Expected exception NoFieldsException has been thrown.'); |
|
733 } |
|
734 |
|
735 $num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
736 $this->assertIdentical($num_records_before, $num_records_after, 'Do nothing as no fields are specified.'); |
|
737 } |
|
738 |
|
739 /** |
|
740 * Test that we can insert fields with values and defaults in the same query. |
|
741 */ |
|
742 function testDefaultInsertWithFields() { |
|
743 $query = db_insert('test') |
|
744 ->fields(array('name' => 'Bob')) |
|
745 ->useDefaults(array('job')); |
|
746 $id = $query->execute(); |
|
747 |
|
748 $schema = drupal_get_schema('test'); |
|
749 |
|
750 $job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField(); |
|
751 $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.'); |
|
752 } |
|
753 } |
|
754 |
|
755 /** |
|
756 * Update builder tests. |
|
757 */ |
|
758 class DatabaseUpdateTestCase extends DatabaseTestCase { |
|
759 |
|
760 public static function getInfo() { |
|
761 return array( |
|
762 'name' => 'Update tests', |
|
763 'description' => 'Test the Update query builder.', |
|
764 'group' => 'Database', |
|
765 ); |
|
766 } |
|
767 |
|
768 /** |
|
769 * Confirm that we can update a single record successfully. |
|
770 */ |
|
771 function testSimpleUpdate() { |
|
772 $num_updated = db_update('test') |
|
773 ->fields(array('name' => 'Tiffany')) |
|
774 ->condition('id', 1) |
|
775 ->execute(); |
|
776 $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); |
|
777 |
|
778 $saved_name = db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 1))->fetchField(); |
|
779 $this->assertIdentical($saved_name, 'Tiffany', 'Updated name successfully.'); |
|
780 } |
|
781 |
|
782 /** |
|
783 * Confirm updating to NULL. |
|
784 */ |
|
785 function testSimpleNullUpdate() { |
|
786 $this->ensureSampleDataNull(); |
|
787 $num_updated = db_update('test_null') |
|
788 ->fields(array('age' => NULL)) |
|
789 ->condition('name', 'Kermit') |
|
790 ->execute(); |
|
791 $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); |
|
792 |
|
793 $saved_age = db_query('SELECT age FROM {test_null} WHERE name = :name', array(':name' => 'Kermit'))->fetchField(); |
|
794 $this->assertNull($saved_age, 'Updated name successfully.'); |
|
795 } |
|
796 |
|
797 /** |
|
798 * Confirm that we can update a multiple records successfully. |
|
799 */ |
|
800 function testMultiUpdate() { |
|
801 $num_updated = db_update('test') |
|
802 ->fields(array('job' => 'Musician')) |
|
803 ->condition('job', 'Singer') |
|
804 ->execute(); |
|
805 $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); |
|
806 |
|
807 $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); |
|
808 $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); |
|
809 } |
|
810 |
|
811 /** |
|
812 * Confirm that we can update a multiple records with a non-equality condition. |
|
813 */ |
|
814 function testMultiGTUpdate() { |
|
815 $num_updated = db_update('test') |
|
816 ->fields(array('job' => 'Musician')) |
|
817 ->condition('age', 26, '>') |
|
818 ->execute(); |
|
819 $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); |
|
820 |
|
821 $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); |
|
822 $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); |
|
823 } |
|
824 |
|
825 /** |
|
826 * Confirm that we can update a multiple records with a where call. |
|
827 */ |
|
828 function testWhereUpdate() { |
|
829 $num_updated = db_update('test') |
|
830 ->fields(array('job' => 'Musician')) |
|
831 ->where('age > :age', array(':age' => 26)) |
|
832 ->execute(); |
|
833 $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); |
|
834 |
|
835 $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); |
|
836 $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); |
|
837 } |
|
838 |
|
839 /** |
|
840 * Confirm that we can stack condition and where calls. |
|
841 */ |
|
842 function testWhereAndConditionUpdate() { |
|
843 $update = db_update('test') |
|
844 ->fields(array('job' => 'Musician')) |
|
845 ->where('age > :age', array(':age' => 26)) |
|
846 ->condition('name', 'Ringo'); |
|
847 $num_updated = $update->execute(); |
|
848 $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); |
|
849 |
|
850 $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); |
|
851 $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); |
|
852 } |
|
853 |
|
854 /** |
|
855 * Test updating with expressions. |
|
856 */ |
|
857 function testExpressionUpdate() { |
|
858 // Set age = 1 for a single row for this test to work. |
|
859 db_update('test') |
|
860 ->condition('id', 1) |
|
861 ->fields(array('age' => 1)) |
|
862 ->execute(); |
|
863 |
|
864 // Ensure that expressions are handled properly. This should set every |
|
865 // record's age to a square of itself, which will change only three of the |
|
866 // four records in the table since 1*1 = 1. That means only three records |
|
867 // are modified, so we should get back 3, not 4, from execute(). |
|
868 $num_rows = db_update('test') |
|
869 ->expression('age', 'age * age') |
|
870 ->execute(); |
|
871 $this->assertIdentical($num_rows, 3, 'Number of affected rows are returned.'); |
|
872 } |
|
873 |
|
874 /** |
|
875 * Confirm that we can update the primary key of a record successfully. |
|
876 */ |
|
877 function testPrimaryKeyUpdate() { |
|
878 $num_updated = db_update('test') |
|
879 ->fields(array('id' => 42, 'name' => 'John')) |
|
880 ->condition('id', 1) |
|
881 ->execute(); |
|
882 $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); |
|
883 |
|
884 $saved_name= db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 42))->fetchField(); |
|
885 $this->assertIdentical($saved_name, 'John', 'Updated primary key successfully.'); |
|
886 } |
|
887 } |
|
888 |
|
889 /** |
|
890 * Tests for more complex update statements. |
|
891 */ |
|
892 class DatabaseUpdateComplexTestCase extends DatabaseTestCase { |
|
893 |
|
894 public static function getInfo() { |
|
895 return array( |
|
896 'name' => 'Update tests, Complex', |
|
897 'description' => 'Test the Update query builder, complex queries.', |
|
898 'group' => 'Database', |
|
899 ); |
|
900 } |
|
901 |
|
902 /** |
|
903 * Test updates with OR conditionals. |
|
904 */ |
|
905 function testOrConditionUpdate() { |
|
906 $update = db_update('test') |
|
907 ->fields(array('job' => 'Musician')) |
|
908 ->condition(db_or() |
|
909 ->condition('name', 'John') |
|
910 ->condition('name', 'Paul') |
|
911 ); |
|
912 $num_updated = $update->execute(); |
|
913 $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); |
|
914 |
|
915 $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); |
|
916 $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); |
|
917 } |
|
918 |
|
919 /** |
|
920 * Test WHERE IN clauses. |
|
921 */ |
|
922 function testInConditionUpdate() { |
|
923 $num_updated = db_update('test') |
|
924 ->fields(array('job' => 'Musician')) |
|
925 ->condition('name', array('John', 'Paul'), 'IN') |
|
926 ->execute(); |
|
927 $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); |
|
928 |
|
929 $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); |
|
930 $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); |
|
931 } |
|
932 |
|
933 /** |
|
934 * Test WHERE NOT IN clauses. |
|
935 */ |
|
936 function testNotInConditionUpdate() { |
|
937 // The o is lowercase in the 'NoT IN' operator, to make sure the operators |
|
938 // work in mixed case. |
|
939 $num_updated = db_update('test') |
|
940 ->fields(array('job' => 'Musician')) |
|
941 ->condition('name', array('John', 'Paul', 'George'), 'NoT IN') |
|
942 ->execute(); |
|
943 $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); |
|
944 |
|
945 $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); |
|
946 $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); |
|
947 } |
|
948 |
|
949 /** |
|
950 * Test BETWEEN conditional clauses. |
|
951 */ |
|
952 function testBetweenConditionUpdate() { |
|
953 $num_updated = db_update('test') |
|
954 ->fields(array('job' => 'Musician')) |
|
955 ->condition('age', array(25, 26), 'BETWEEN') |
|
956 ->execute(); |
|
957 $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); |
|
958 |
|
959 $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); |
|
960 $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); |
|
961 } |
|
962 |
|
963 /** |
|
964 * Test LIKE conditionals. |
|
965 */ |
|
966 function testLikeConditionUpdate() { |
|
967 $num_updated = db_update('test') |
|
968 ->fields(array('job' => 'Musician')) |
|
969 ->condition('name', '%ge%', 'LIKE') |
|
970 ->execute(); |
|
971 $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); |
|
972 |
|
973 $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); |
|
974 $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); |
|
975 } |
|
976 |
|
977 /** |
|
978 * Test update with expression values. |
|
979 */ |
|
980 function testUpdateExpression() { |
|
981 $before_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField(); |
|
982 $GLOBALS['larry_test'] = 1; |
|
983 $num_updated = db_update('test') |
|
984 ->condition('name', 'Ringo') |
|
985 ->fields(array('job' => 'Musician')) |
|
986 ->expression('age', 'age + :age', array(':age' => 4)) |
|
987 ->execute(); |
|
988 $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); |
|
989 |
|
990 $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); |
|
991 $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); |
|
992 |
|
993 $person = db_query('SELECT * FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetch(); |
|
994 $this->assertEqual($person->name, 'Ringo', 'Name set correctly.'); |
|
995 $this->assertEqual($person->age, $before_age + 4, 'Age set correctly.'); |
|
996 $this->assertEqual($person->job, 'Musician', 'Job set correctly.'); |
|
997 $GLOBALS['larry_test'] = 0; |
|
998 } |
|
999 |
|
1000 /** |
|
1001 * Test update with only expression values. |
|
1002 */ |
|
1003 function testUpdateOnlyExpression() { |
|
1004 $before_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField(); |
|
1005 $num_updated = db_update('test') |
|
1006 ->condition('name', 'Ringo') |
|
1007 ->expression('age', 'age + :age', array(':age' => 4)) |
|
1008 ->execute(); |
|
1009 $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); |
|
1010 |
|
1011 $after_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField(); |
|
1012 $this->assertEqual($before_age + 4, $after_age, 'Age updated correctly'); |
|
1013 } |
|
1014 } |
|
1015 |
|
1016 /** |
|
1017 * Test update queries involving LOB values. |
|
1018 */ |
|
1019 class DatabaseUpdateLOBTestCase extends DatabaseTestCase { |
|
1020 |
|
1021 public static function getInfo() { |
|
1022 return array( |
|
1023 'name' => 'Update tests, LOB', |
|
1024 'description' => 'Test the Update query builder with LOB fields.', |
|
1025 'group' => 'Database', |
|
1026 ); |
|
1027 } |
|
1028 |
|
1029 /** |
|
1030 * Confirm that we can update a blob column. |
|
1031 */ |
|
1032 function testUpdateOneBlob() { |
|
1033 $data = "This is\000a test."; |
|
1034 $this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.'); |
|
1035 $id = db_insert('test_one_blob') |
|
1036 ->fields(array('blob1' => $data)) |
|
1037 ->execute(); |
|
1038 |
|
1039 $data .= $data; |
|
1040 db_update('test_one_blob') |
|
1041 ->condition('id', $id) |
|
1042 ->fields(array('blob1' => $data)) |
|
1043 ->execute(); |
|
1044 |
|
1045 $r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc(); |
|
1046 $this->assertTrue($r['blob1'] === $data, format_string('Can update a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r)))); |
|
1047 } |
|
1048 |
|
1049 /** |
|
1050 * Confirm that we can update two blob columns in the same table. |
|
1051 */ |
|
1052 function testUpdateMultipleBlob() { |
|
1053 $id = db_insert('test_two_blobs') |
|
1054 ->fields(array( |
|
1055 'blob1' => 'This is', |
|
1056 'blob2' => 'a test', |
|
1057 )) |
|
1058 ->execute(); |
|
1059 |
|
1060 db_update('test_two_blobs') |
|
1061 ->condition('id', $id) |
|
1062 ->fields(array('blob1' => 'and so', 'blob2' => 'is this')) |
|
1063 ->execute(); |
|
1064 |
|
1065 $r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc(); |
|
1066 $this->assertTrue($r['blob1'] === 'and so' && $r['blob2'] === 'is this', 'Can update multiple blobs per row.'); |
|
1067 } |
|
1068 } |
|
1069 |
|
1070 /** |
|
1071 * Delete/Truncate tests. |
|
1072 * |
|
1073 * The DELETE tests are not as extensive, as all of the interesting code for |
|
1074 * DELETE queries is in the conditional which is identical to the UPDATE and |
|
1075 * SELECT conditional handling. |
|
1076 * |
|
1077 * The TRUNCATE tests are not extensive either, because the behavior of |
|
1078 * TRUNCATE queries is not consistent across database engines. We only test |
|
1079 * that a TRUNCATE query actually deletes all rows from the target table. |
|
1080 */ |
|
1081 class DatabaseDeleteTruncateTestCase extends DatabaseTestCase { |
|
1082 |
|
1083 public static function getInfo() { |
|
1084 return array( |
|
1085 'name' => 'Delete/Truncate tests', |
|
1086 'description' => 'Test the Delete and Truncate query builders.', |
|
1087 'group' => 'Database', |
|
1088 ); |
|
1089 } |
|
1090 |
|
1091 /** |
|
1092 * Confirm that we can use a subselect in a delete successfully. |
|
1093 */ |
|
1094 function testSubselectDelete() { |
|
1095 $num_records_before = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField(); |
|
1096 $pid_to_delete = db_query("SELECT * FROM {test_task} WHERE task = 'sleep'")->fetchField(); |
|
1097 |
|
1098 $subquery = db_select('test', 't') |
|
1099 ->fields('t', array('id')) |
|
1100 ->condition('t.id', array($pid_to_delete), 'IN'); |
|
1101 $delete = db_delete('test_task') |
|
1102 ->condition('task', 'sleep') |
|
1103 ->condition('pid', $subquery, 'IN'); |
|
1104 |
|
1105 $num_deleted = $delete->execute(); |
|
1106 $this->assertEqual($num_deleted, 1, "Deleted 1 record."); |
|
1107 |
|
1108 $num_records_after = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField(); |
|
1109 $this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.'); |
|
1110 } |
|
1111 |
|
1112 /** |
|
1113 * Confirm that we can delete a single record successfully. |
|
1114 */ |
|
1115 function testSimpleDelete() { |
|
1116 $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
1117 |
|
1118 $num_deleted = db_delete('test') |
|
1119 ->condition('id', 1) |
|
1120 ->execute(); |
|
1121 $this->assertIdentical($num_deleted, 1, 'Deleted 1 record.'); |
|
1122 |
|
1123 $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
1124 $this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.'); |
|
1125 } |
|
1126 |
|
1127 /** |
|
1128 * Confirm that we can truncate a whole table successfully. |
|
1129 */ |
|
1130 function testTruncate() { |
|
1131 $num_records_before = db_query("SELECT COUNT(*) FROM {test}")->fetchField(); |
|
1132 |
|
1133 db_truncate('test')->execute(); |
|
1134 |
|
1135 $num_records_after = db_query("SELECT COUNT(*) FROM {test}")->fetchField(); |
|
1136 $this->assertEqual(0, $num_records_after, 'Truncate really deletes everything.'); |
|
1137 } |
|
1138 } |
|
1139 |
|
1140 /** |
|
1141 * Test the MERGE query builder. |
|
1142 */ |
|
1143 class DatabaseMergeTestCase extends DatabaseTestCase { |
|
1144 |
|
1145 public static function getInfo() { |
|
1146 return array( |
|
1147 'name' => 'Merge tests', |
|
1148 'description' => 'Test the Merge query builder.', |
|
1149 'group' => 'Database', |
|
1150 ); |
|
1151 } |
|
1152 |
|
1153 /** |
|
1154 * Confirm that we can merge-insert a record successfully. |
|
1155 */ |
|
1156 function testMergeInsert() { |
|
1157 $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1158 |
|
1159 $result = db_merge('test_people') |
|
1160 ->key(array('job' => 'Presenter')) |
|
1161 ->fields(array( |
|
1162 'age' => 31, |
|
1163 'name' => 'Tiffany', |
|
1164 )) |
|
1165 ->execute(); |
|
1166 |
|
1167 $this->assertEqual($result, MergeQuery::STATUS_INSERT, 'Insert status returned.'); |
|
1168 |
|
1169 $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1170 $this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.'); |
|
1171 |
|
1172 $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch(); |
|
1173 $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); |
|
1174 $this->assertEqual($person->age, 31, 'Age set correctly.'); |
|
1175 $this->assertEqual($person->job, 'Presenter', 'Job set correctly.'); |
|
1176 } |
|
1177 |
|
1178 /** |
|
1179 * Confirm that we can merge-update a record successfully. |
|
1180 */ |
|
1181 function testMergeUpdate() { |
|
1182 $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1183 |
|
1184 $result = db_merge('test_people') |
|
1185 ->key(array('job' => 'Speaker')) |
|
1186 ->fields(array( |
|
1187 'age' => 31, |
|
1188 'name' => 'Tiffany', |
|
1189 )) |
|
1190 ->execute(); |
|
1191 |
|
1192 $this->assertEqual($result, MergeQuery::STATUS_UPDATE, 'Update status returned.'); |
|
1193 |
|
1194 $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1195 $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); |
|
1196 |
|
1197 $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); |
|
1198 $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); |
|
1199 $this->assertEqual($person->age, 31, 'Age set correctly.'); |
|
1200 $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); |
|
1201 } |
|
1202 |
|
1203 /** |
|
1204 * Confirm that we can merge-update a record successfully, with different insert and update. |
|
1205 */ |
|
1206 function testMergeUpdateExcept() { |
|
1207 $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1208 |
|
1209 db_merge('test_people') |
|
1210 ->key(array('job' => 'Speaker')) |
|
1211 ->insertFields(array('age' => 31)) |
|
1212 ->updateFields(array('name' => 'Tiffany')) |
|
1213 ->execute(); |
|
1214 |
|
1215 $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1216 $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); |
|
1217 |
|
1218 $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); |
|
1219 $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); |
|
1220 $this->assertEqual($person->age, 30, 'Age skipped correctly.'); |
|
1221 $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); |
|
1222 } |
|
1223 |
|
1224 /** |
|
1225 * Confirm that we can merge-update a record successfully, with alternate replacement. |
|
1226 */ |
|
1227 function testMergeUpdateExplicit() { |
|
1228 $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1229 |
|
1230 db_merge('test_people') |
|
1231 ->key(array('job' => 'Speaker')) |
|
1232 ->insertFields(array( |
|
1233 'age' => 31, |
|
1234 'name' => 'Tiffany', |
|
1235 )) |
|
1236 ->updateFields(array( |
|
1237 'name' => 'Joe', |
|
1238 )) |
|
1239 ->execute(); |
|
1240 |
|
1241 $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1242 $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); |
|
1243 |
|
1244 $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); |
|
1245 $this->assertEqual($person->name, 'Joe', 'Name set correctly.'); |
|
1246 $this->assertEqual($person->age, 30, 'Age skipped correctly.'); |
|
1247 $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); |
|
1248 } |
|
1249 |
|
1250 /** |
|
1251 * Confirm that we can merge-update a record successfully, with expressions. |
|
1252 */ |
|
1253 function testMergeUpdateExpression() { |
|
1254 $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1255 |
|
1256 $age_before = db_query('SELECT age FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetchField(); |
|
1257 |
|
1258 // This is a very contrived example, as I have no idea why you'd want to |
|
1259 // change age this way, but that's beside the point. |
|
1260 // Note that we are also double-setting age here, once as a literal and |
|
1261 // once as an expression. This test will only pass if the expression wins, |
|
1262 // which is what is supposed to happen. |
|
1263 db_merge('test_people') |
|
1264 ->key(array('job' => 'Speaker')) |
|
1265 ->fields(array('name' => 'Tiffany')) |
|
1266 ->insertFields(array('age' => 31)) |
|
1267 ->expression('age', 'age + :age', array(':age' => 4)) |
|
1268 ->execute(); |
|
1269 |
|
1270 $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1271 $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); |
|
1272 |
|
1273 $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); |
|
1274 $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); |
|
1275 $this->assertEqual($person->age, $age_before + 4, 'Age updated correctly.'); |
|
1276 $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); |
|
1277 } |
|
1278 |
|
1279 /** |
|
1280 * Test that we can merge-insert without any update fields. |
|
1281 */ |
|
1282 function testMergeInsertWithoutUpdate() { |
|
1283 $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1284 |
|
1285 db_merge('test_people') |
|
1286 ->key(array('job' => 'Presenter')) |
|
1287 ->execute(); |
|
1288 |
|
1289 $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1290 $this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.'); |
|
1291 |
|
1292 $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch(); |
|
1293 $this->assertEqual($person->name, '', 'Name set correctly.'); |
|
1294 $this->assertEqual($person->age, 0, 'Age set correctly.'); |
|
1295 $this->assertEqual($person->job, 'Presenter', 'Job set correctly.'); |
|
1296 } |
|
1297 |
|
1298 /** |
|
1299 * Confirm that we can merge-update without any update fields. |
|
1300 */ |
|
1301 function testMergeUpdateWithoutUpdate() { |
|
1302 $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1303 |
|
1304 db_merge('test_people') |
|
1305 ->key(array('job' => 'Speaker')) |
|
1306 ->execute(); |
|
1307 |
|
1308 $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1309 $this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.'); |
|
1310 |
|
1311 $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); |
|
1312 $this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.'); |
|
1313 $this->assertEqual($person->age, 30, 'Age skipped correctly.'); |
|
1314 $this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.'); |
|
1315 |
|
1316 db_merge('test_people') |
|
1317 ->key(array('job' => 'Speaker')) |
|
1318 ->insertFields(array('age' => 31)) |
|
1319 ->execute(); |
|
1320 |
|
1321 $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); |
|
1322 $this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.'); |
|
1323 |
|
1324 $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); |
|
1325 $this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.'); |
|
1326 $this->assertEqual($person->age, 30, 'Age skipped correctly.'); |
|
1327 $this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.'); |
|
1328 } |
|
1329 |
|
1330 /** |
|
1331 * Test that an invalid merge query throws an exception like it is supposed to. |
|
1332 */ |
|
1333 function testInvalidMerge() { |
|
1334 try { |
|
1335 // This query should die because there is no key field specified. |
|
1336 db_merge('test_people') |
|
1337 ->fields(array( |
|
1338 'age' => 31, |
|
1339 'name' => 'Tiffany', |
|
1340 )) |
|
1341 ->execute(); |
|
1342 } |
|
1343 catch (InvalidMergeQueryException $e) { |
|
1344 $this->pass('InvalidMergeQueryException thrown for invalid query.'); |
|
1345 return; |
|
1346 } |
|
1347 $this->fail('No InvalidMergeQueryException thrown'); |
|
1348 } |
|
1349 } |
|
1350 |
|
1351 /** |
|
1352 * Test the SELECT builder. |
|
1353 */ |
|
1354 class DatabaseSelectTestCase extends DatabaseTestCase { |
|
1355 |
|
1356 public static function getInfo() { |
|
1357 return array( |
|
1358 'name' => 'Select tests', |
|
1359 'description' => 'Test the Select query builder.', |
|
1360 'group' => 'Database', |
|
1361 ); |
|
1362 } |
|
1363 |
|
1364 /** |
|
1365 * Test rudimentary SELECT statements. |
|
1366 */ |
|
1367 function testSimpleSelect() { |
|
1368 $query = db_select('test'); |
|
1369 $name_field = $query->addField('test', 'name'); |
|
1370 $age_field = $query->addField('test', 'age', 'age'); |
|
1371 $result = $query->execute(); |
|
1372 |
|
1373 $num_records = 0; |
|
1374 foreach ($result as $record) { |
|
1375 $num_records++; |
|
1376 } |
|
1377 |
|
1378 $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); |
|
1379 } |
|
1380 |
|
1381 /** |
|
1382 * Test rudimentary SELECT statement with a COMMENT. |
|
1383 */ |
|
1384 function testSimpleComment() { |
|
1385 $query = db_select('test')->comment('Testing query comments'); |
|
1386 $name_field = $query->addField('test', 'name'); |
|
1387 $age_field = $query->addField('test', 'age', 'age'); |
|
1388 $result = $query->execute(); |
|
1389 |
|
1390 $num_records = 0; |
|
1391 foreach ($result as $record) { |
|
1392 $num_records++; |
|
1393 } |
|
1394 |
|
1395 $query = (string)$query; |
|
1396 $expected = "/* Testing query comments */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test"; |
|
1397 |
|
1398 $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); |
|
1399 $this->assertEqual($query, $expected, 'The flattened query contains the comment string.'); |
|
1400 } |
|
1401 |
|
1402 /** |
|
1403 * Test query COMMENT system against vulnerabilities. |
|
1404 */ |
|
1405 function testVulnerableComment() { |
|
1406 $query = db_select('test')->comment('Testing query comments */ SELECT nid FROM {node}; --'); |
|
1407 $name_field = $query->addField('test', 'name'); |
|
1408 $age_field = $query->addField('test', 'age', 'age'); |
|
1409 $result = $query->execute(); |
|
1410 |
|
1411 $num_records = 0; |
|
1412 foreach ($result as $record) { |
|
1413 $num_records++; |
|
1414 } |
|
1415 |
|
1416 $query = (string)$query; |
|
1417 $expected = "/* Testing query comments * / SELECT nid FROM {node}; -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test"; |
|
1418 |
|
1419 $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); |
|
1420 $this->assertEqual($query, $expected, 'The flattened query contains the sanitised comment string.'); |
|
1421 |
|
1422 $connection = Database::getConnection(); |
|
1423 foreach ($this->makeCommentsProvider() as $test_set) { |
|
1424 list($expected, $comments) = $test_set; |
|
1425 $this->assertEqual($expected, $connection->makeComment($comments)); |
|
1426 } |
|
1427 } |
|
1428 |
|
1429 /** |
|
1430 * Provides expected and input values for testVulnerableComment(). |
|
1431 */ |
|
1432 function makeCommentsProvider() { |
|
1433 return array( |
|
1434 array( |
|
1435 '/* */ ', |
|
1436 array(''), |
|
1437 ), |
|
1438 // Try and close the comment early. |
|
1439 array( |
|
1440 '/* Exploit * / DROP TABLE node; -- */ ', |
|
1441 array('Exploit */ DROP TABLE node; --'), |
|
1442 ), |
|
1443 // Variations on comment closing. |
|
1444 array( |
|
1445 '/* Exploit * / * / DROP TABLE node; -- */ ', |
|
1446 array('Exploit */*/ DROP TABLE node; --'), |
|
1447 ), |
|
1448 array( |
|
1449 '/* Exploit * * // DROP TABLE node; -- */ ', |
|
1450 array('Exploit **// DROP TABLE node; --'), |
|
1451 ), |
|
1452 // Try closing the comment in the second string which is appended. |
|
1453 array( |
|
1454 '/* Exploit * / DROP TABLE node; --; Another try * / DROP TABLE node; -- */ ', |
|
1455 array('Exploit */ DROP TABLE node; --', 'Another try */ DROP TABLE node; --'), |
|
1456 ), |
|
1457 ); |
|
1458 } |
|
1459 |
|
1460 /** |
|
1461 * Test basic conditionals on SELECT statements. |
|
1462 */ |
|
1463 function testSimpleSelectConditional() { |
|
1464 $query = db_select('test'); |
|
1465 $name_field = $query->addField('test', 'name'); |
|
1466 $age_field = $query->addField('test', 'age', 'age'); |
|
1467 $query->condition('age', 27); |
|
1468 $result = $query->execute(); |
|
1469 |
|
1470 // Check that the aliases are being created the way we want. |
|
1471 $this->assertEqual($name_field, 'name', 'Name field alias is correct.'); |
|
1472 $this->assertEqual($age_field, 'age', 'Age field alias is correct.'); |
|
1473 |
|
1474 // Ensure that we got the right record. |
|
1475 $record = $result->fetch(); |
|
1476 $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); |
|
1477 $this->assertEqual($record->$age_field, 27, 'Fetched age is correct.'); |
|
1478 } |
|
1479 |
|
1480 /** |
|
1481 * Test SELECT statements with expressions. |
|
1482 */ |
|
1483 function testSimpleSelectExpression() { |
|
1484 $query = db_select('test'); |
|
1485 $name_field = $query->addField('test', 'name'); |
|
1486 $age_field = $query->addExpression("age*2", 'double_age'); |
|
1487 $query->condition('age', 27); |
|
1488 $result = $query->execute(); |
|
1489 |
|
1490 // Check that the aliases are being created the way we want. |
|
1491 $this->assertEqual($name_field, 'name', 'Name field alias is correct.'); |
|
1492 $this->assertEqual($age_field, 'double_age', 'Age field alias is correct.'); |
|
1493 |
|
1494 // Ensure that we got the right record. |
|
1495 $record = $result->fetch(); |
|
1496 $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); |
|
1497 $this->assertEqual($record->$age_field, 27*2, 'Fetched age expression is correct.'); |
|
1498 } |
|
1499 |
|
1500 /** |
|
1501 * Test SELECT statements with multiple expressions. |
|
1502 */ |
|
1503 function testSimpleSelectExpressionMultiple() { |
|
1504 $query = db_select('test'); |
|
1505 $name_field = $query->addField('test', 'name'); |
|
1506 $age_double_field = $query->addExpression("age*2"); |
|
1507 $age_triple_field = $query->addExpression("age*3"); |
|
1508 $query->condition('age', 27); |
|
1509 $result = $query->execute(); |
|
1510 |
|
1511 // Check that the aliases are being created the way we want. |
|
1512 $this->assertEqual($age_double_field, 'expression', 'Double age field alias is correct.'); |
|
1513 $this->assertEqual($age_triple_field, 'expression_2', 'Triple age field alias is correct.'); |
|
1514 |
|
1515 // Ensure that we got the right record. |
|
1516 $record = $result->fetch(); |
|
1517 $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); |
|
1518 $this->assertEqual($record->$age_double_field, 27*2, 'Fetched double age expression is correct.'); |
|
1519 $this->assertEqual($record->$age_triple_field, 27*3, 'Fetched triple age expression is correct.'); |
|
1520 } |
|
1521 |
|
1522 /** |
|
1523 * Test adding multiple fields to a select statement at the same time. |
|
1524 */ |
|
1525 function testSimpleSelectMultipleFields() { |
|
1526 $record = db_select('test') |
|
1527 ->fields('test', array('id', 'name', 'age', 'job')) |
|
1528 ->condition('age', 27) |
|
1529 ->execute()->fetchObject(); |
|
1530 |
|
1531 // Check that all fields we asked for are present. |
|
1532 $this->assertNotNull($record->id, 'ID field is present.'); |
|
1533 $this->assertNotNull($record->name, 'Name field is present.'); |
|
1534 $this->assertNotNull($record->age, 'Age field is present.'); |
|
1535 $this->assertNotNull($record->job, 'Job field is present.'); |
|
1536 |
|
1537 // Ensure that we got the right record. |
|
1538 // Check that all fields we asked for are present. |
|
1539 $this->assertEqual($record->id, 2, 'ID field has the correct value.'); |
|
1540 $this->assertEqual($record->name, 'George', 'Name field has the correct value.'); |
|
1541 $this->assertEqual($record->age, 27, 'Age field has the correct value.'); |
|
1542 $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.'); |
|
1543 } |
|
1544 |
|
1545 /** |
|
1546 * Test adding all fields from a given table to a select statement. |
|
1547 */ |
|
1548 function testSimpleSelectAllFields() { |
|
1549 $record = db_select('test') |
|
1550 ->fields('test') |
|
1551 ->condition('age', 27) |
|
1552 ->execute()->fetchObject(); |
|
1553 |
|
1554 // Check that all fields we asked for are present. |
|
1555 $this->assertNotNull($record->id, 'ID field is present.'); |
|
1556 $this->assertNotNull($record->name, 'Name field is present.'); |
|
1557 $this->assertNotNull($record->age, 'Age field is present.'); |
|
1558 $this->assertNotNull($record->job, 'Job field is present.'); |
|
1559 |
|
1560 // Ensure that we got the right record. |
|
1561 // Check that all fields we asked for are present. |
|
1562 $this->assertEqual($record->id, 2, 'ID field has the correct value.'); |
|
1563 $this->assertEqual($record->name, 'George', 'Name field has the correct value.'); |
|
1564 $this->assertEqual($record->age, 27, 'Age field has the correct value.'); |
|
1565 $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.'); |
|
1566 } |
|
1567 |
|
1568 /** |
|
1569 * Test that we can find a record with a NULL value. |
|
1570 */ |
|
1571 function testNullCondition() { |
|
1572 $this->ensureSampleDataNull(); |
|
1573 |
|
1574 $names = db_select('test_null', 'tn') |
|
1575 ->fields('tn', array('name')) |
|
1576 ->isNull('age') |
|
1577 ->execute()->fetchCol(); |
|
1578 |
|
1579 $this->assertEqual(count($names), 1, 'Correct number of records found with NULL age.'); |
|
1580 $this->assertEqual($names[0], 'Fozzie', 'Correct record returned for NULL age.'); |
|
1581 } |
|
1582 |
|
1583 /** |
|
1584 * Test that we can find a record without a NULL value. |
|
1585 */ |
|
1586 function testNotNullCondition() { |
|
1587 $this->ensureSampleDataNull(); |
|
1588 |
|
1589 $names = db_select('test_null', 'tn') |
|
1590 ->fields('tn', array('name')) |
|
1591 ->isNotNull('tn.age') |
|
1592 ->orderBy('name') |
|
1593 ->execute()->fetchCol(); |
|
1594 |
|
1595 $this->assertEqual(count($names), 2, 'Correct number of records found withNOT NULL age.'); |
|
1596 $this->assertEqual($names[0], 'Gonzo', 'Correct record returned for NOT NULL age.'); |
|
1597 $this->assertEqual($names[1], 'Kermit', 'Correct record returned for NOT NULL age.'); |
|
1598 } |
|
1599 |
|
1600 /** |
|
1601 * Test that we can UNION multiple Select queries together. This is |
|
1602 * semantically equal to UNION DISTINCT, so we don't explicity test that. |
|
1603 */ |
|
1604 function testUnion() { |
|
1605 $query_1 = db_select('test', 't') |
|
1606 ->fields('t', array('name')) |
|
1607 ->condition('age', array(27, 28), 'IN'); |
|
1608 |
|
1609 $query_2 = db_select('test', 't') |
|
1610 ->fields('t', array('name')) |
|
1611 ->condition('age', 28); |
|
1612 |
|
1613 $query_1->union($query_2); |
|
1614 |
|
1615 $names = $query_1->execute()->fetchCol(); |
|
1616 |
|
1617 // Ensure we only get 2 records. |
|
1618 $this->assertEqual(count($names), 2, 'UNION correctly discarded duplicates.'); |
|
1619 |
|
1620 $this->assertEqual($names[0], 'George', 'First query returned correct name.'); |
|
1621 $this->assertEqual($names[1], 'Ringo', 'Second query returned correct name.'); |
|
1622 } |
|
1623 |
|
1624 /** |
|
1625 * Test that we can UNION ALL multiple Select queries together. |
|
1626 */ |
|
1627 function testUnionAll() { |
|
1628 $query_1 = db_select('test', 't') |
|
1629 ->fields('t', array('name')) |
|
1630 ->condition('age', array(27, 28), 'IN'); |
|
1631 |
|
1632 $query_2 = db_select('test', 't') |
|
1633 ->fields('t', array('name')) |
|
1634 ->condition('age', 28); |
|
1635 |
|
1636 $query_1->union($query_2, 'ALL'); |
|
1637 |
|
1638 $names = $query_1->execute()->fetchCol(); |
|
1639 |
|
1640 // Ensure we get all 3 records. |
|
1641 $this->assertEqual(count($names), 3, 'UNION ALL correctly preserved duplicates.'); |
|
1642 |
|
1643 $this->assertEqual($names[0], 'George', 'First query returned correct first name.'); |
|
1644 $this->assertEqual($names[1], 'Ringo', 'Second query returned correct second name.'); |
|
1645 $this->assertEqual($names[2], 'Ringo', 'Third query returned correct name.'); |
|
1646 } |
|
1647 |
|
1648 /** |
|
1649 * Test that random ordering of queries works. |
|
1650 * |
|
1651 * We take the approach of testing the Drupal layer only, rather than trying |
|
1652 * to test that the database's random number generator actually produces |
|
1653 * random queries (which is very difficult to do without an unacceptable risk |
|
1654 * of the test failing by accident). |
|
1655 * |
|
1656 * Therefore, in this test we simply run the same query twice and assert that |
|
1657 * the two results are reordered versions of each other (as well as of the |
|
1658 * same query without the random ordering). It is reasonable to assume that |
|
1659 * if we run the same select query twice and the results are in a different |
|
1660 * order each time, the only way this could happen is if we have successfully |
|
1661 * triggered the database's random ordering functionality. |
|
1662 */ |
|
1663 function testRandomOrder() { |
|
1664 // Use 52 items, so the chance that this test fails by accident will be the |
|
1665 // same as the chance that a deck of cards will come out in the same order |
|
1666 // after shuffling it (in other words, nearly impossible). |
|
1667 $number_of_items = 52; |
|
1668 while (db_query("SELECT MAX(id) FROM {test}")->fetchField() < $number_of_items) { |
|
1669 db_insert('test')->fields(array('name' => $this->randomName()))->execute(); |
|
1670 } |
|
1671 |
|
1672 // First select the items in order and make sure we get an ordered list. |
|
1673 $expected_ids = range(1, $number_of_items); |
|
1674 $ordered_ids = db_select('test', 't') |
|
1675 ->fields('t', array('id')) |
|
1676 ->range(0, $number_of_items) |
|
1677 ->orderBy('id') |
|
1678 ->execute() |
|
1679 ->fetchCol(); |
|
1680 $this->assertEqual($ordered_ids, $expected_ids, 'A query without random ordering returns IDs in the correct order.'); |
|
1681 |
|
1682 // Now perform the same query, but instead choose a random ordering. We |
|
1683 // expect this to contain a differently ordered version of the original |
|
1684 // result. |
|
1685 $randomized_ids = db_select('test', 't') |
|
1686 ->fields('t', array('id')) |
|
1687 ->range(0, $number_of_items) |
|
1688 ->orderRandom() |
|
1689 ->execute() |
|
1690 ->fetchCol(); |
|
1691 $this->assertNotEqual($randomized_ids, $ordered_ids, 'A query with random ordering returns an unordered set of IDs.'); |
|
1692 $sorted_ids = $randomized_ids; |
|
1693 sort($sorted_ids); |
|
1694 $this->assertEqual($sorted_ids, $ordered_ids, 'After sorting the random list, the result matches the original query.'); |
|
1695 |
|
1696 // Now perform the exact same query again, and make sure the order is |
|
1697 // different. |
|
1698 $randomized_ids_second_set = db_select('test', 't') |
|
1699 ->fields('t', array('id')) |
|
1700 ->range(0, $number_of_items) |
|
1701 ->orderRandom() |
|
1702 ->execute() |
|
1703 ->fetchCol(); |
|
1704 $this->assertNotEqual($randomized_ids_second_set, $randomized_ids, 'Performing the query with random ordering a second time returns IDs in a different order.'); |
|
1705 $sorted_ids_second_set = $randomized_ids_second_set; |
|
1706 sort($sorted_ids_second_set); |
|
1707 $this->assertEqual($sorted_ids_second_set, $sorted_ids, 'After sorting the second random list, the result matches the sorted version of the first random list.'); |
|
1708 } |
|
1709 |
|
1710 /** |
|
1711 * Test that aliases are renamed when duplicates. |
|
1712 */ |
|
1713 function testSelectDuplicateAlias() { |
|
1714 $query = db_select('test', 't'); |
|
1715 $alias1 = $query->addField('t', 'name', 'the_alias'); |
|
1716 $alias2 = $query->addField('t', 'age', 'the_alias'); |
|
1717 $this->assertNotIdentical($alias1, $alias2, 'Duplicate aliases are renamed.'); |
|
1718 } |
|
1719 } |
|
1720 |
|
1721 /** |
|
1722 * Test case for subselects in a dynamic SELECT query. |
|
1723 */ |
|
1724 class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { |
|
1725 |
|
1726 public static function getInfo() { |
|
1727 return array( |
|
1728 'name' => 'Select tests, subqueries', |
|
1729 'description' => 'Test the Select query builder.', |
|
1730 'group' => 'Database', |
|
1731 ); |
|
1732 } |
|
1733 |
|
1734 /** |
|
1735 * Test that we can use a subquery in a FROM clause. |
|
1736 */ |
|
1737 function testFromSubquerySelect() { |
|
1738 // Create a subquery, which is just a normal query object. |
|
1739 $subquery = db_select('test_task', 'tt'); |
|
1740 $subquery->addField('tt', 'pid', 'pid'); |
|
1741 $subquery->addField('tt', 'task', 'task'); |
|
1742 $subquery->condition('priority', 1); |
|
1743 |
|
1744 for ($i = 0; $i < 2; $i++) { |
|
1745 // Create another query that joins against the virtual table resulting |
|
1746 // from the subquery. |
|
1747 $select = db_select($subquery, 'tt2'); |
|
1748 $select->join('test', 't', 't.id=tt2.pid'); |
|
1749 $select->addField('t', 'name'); |
|
1750 if ($i) { |
|
1751 // Use a different number of conditions here to confuse the subquery |
|
1752 // placeholder counter, testing http://drupal.org/node/1112854. |
|
1753 $select->condition('name', 'John'); |
|
1754 } |
|
1755 $select->condition('task', 'code'); |
|
1756 |
|
1757 // The resulting query should be equivalent to: |
|
1758 // SELECT t.name |
|
1759 // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt WHERE priority=1) tt |
|
1760 // INNER JOIN test t ON t.id=tt.pid |
|
1761 // WHERE tt.task = 'code' |
|
1762 $people = $select->execute()->fetchCol(); |
|
1763 |
|
1764 $this->assertEqual(count($people), 1, 'Returned the correct number of rows.'); |
|
1765 } |
|
1766 } |
|
1767 |
|
1768 /** |
|
1769 * Test that we can use a subquery in a FROM clause with a limit. |
|
1770 */ |
|
1771 function testFromSubquerySelectWithLimit() { |
|
1772 // Create a subquery, which is just a normal query object. |
|
1773 $subquery = db_select('test_task', 'tt'); |
|
1774 $subquery->addField('tt', 'pid', 'pid'); |
|
1775 $subquery->addField('tt', 'task', 'task'); |
|
1776 $subquery->orderBy('priority', 'DESC'); |
|
1777 $subquery->range(0, 1); |
|
1778 |
|
1779 // Create another query that joins against the virtual table resulting |
|
1780 // from the subquery. |
|
1781 $select = db_select($subquery, 'tt2'); |
|
1782 $select->join('test', 't', 't.id=tt2.pid'); |
|
1783 $select->addField('t', 'name'); |
|
1784 |
|
1785 // The resulting query should be equivalent to: |
|
1786 // SELECT t.name |
|
1787 // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt ORDER BY priority DESC LIMIT 1 OFFSET 0) tt |
|
1788 // INNER JOIN test t ON t.id=tt.pid |
|
1789 $people = $select->execute()->fetchCol(); |
|
1790 |
|
1791 $this->assertEqual(count($people), 1, 'Returned the correct number of rows.'); |
|
1792 } |
|
1793 |
|
1794 /** |
|
1795 * Test that we can use a subquery in a WHERE clause. |
|
1796 */ |
|
1797 function testConditionSubquerySelect() { |
|
1798 // Create a subquery, which is just a normal query object. |
|
1799 $subquery = db_select('test_task', 'tt'); |
|
1800 $subquery->addField('tt', 'pid', 'pid'); |
|
1801 $subquery->condition('tt.priority', 1); |
|
1802 |
|
1803 // Create another query that joins against the virtual table resulting |
|
1804 // from the subquery. |
|
1805 $select = db_select('test_task', 'tt2'); |
|
1806 $select->addField('tt2', 'task'); |
|
1807 $select->condition('tt2.pid', $subquery, 'IN'); |
|
1808 |
|
1809 // The resulting query should be equivalent to: |
|
1810 // SELECT tt2.name |
|
1811 // FROM test tt2 |
|
1812 // WHERE tt2.pid IN (SELECT tt.pid AS pid FROM test_task tt WHERE tt.priority=1) |
|
1813 $people = $select->execute()->fetchCol(); |
|
1814 $this->assertEqual(count($people), 5, 'Returned the correct number of rows.'); |
|
1815 } |
|
1816 |
|
1817 /** |
|
1818 * Test that we can use a subquery in a JOIN clause. |
|
1819 */ |
|
1820 function testJoinSubquerySelect() { |
|
1821 // Create a subquery, which is just a normal query object. |
|
1822 $subquery = db_select('test_task', 'tt'); |
|
1823 $subquery->addField('tt', 'pid', 'pid'); |
|
1824 $subquery->condition('priority', 1); |
|
1825 |
|
1826 // Create another query that joins against the virtual table resulting |
|
1827 // from the subquery. |
|
1828 $select = db_select('test', 't'); |
|
1829 $select->join($subquery, 'tt', 't.id=tt.pid'); |
|
1830 $select->addField('t', 'name'); |
|
1831 |
|
1832 // The resulting query should be equivalent to: |
|
1833 // SELECT t.name |
|
1834 // FROM test t |
|
1835 // INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid |
|
1836 $people = $select->execute()->fetchCol(); |
|
1837 |
|
1838 $this->assertEqual(count($people), 2, 'Returned the correct number of rows.'); |
|
1839 } |
|
1840 |
|
1841 /** |
|
1842 * Test EXISTS subquery conditionals on SELECT statements. |
|
1843 * |
|
1844 * We essentially select all rows from the {test} table that have matching |
|
1845 * rows in the {test_people} table based on the shared name column. |
|
1846 */ |
|
1847 function testExistsSubquerySelect() { |
|
1848 // Put George into {test_people}. |
|
1849 db_insert('test_people') |
|
1850 ->fields(array( |
|
1851 'name' => 'George', |
|
1852 'age' => 27, |
|
1853 'job' => 'Singer', |
|
1854 )) |
|
1855 ->execute(); |
|
1856 // Base query to {test}. |
|
1857 $query = db_select('test', 't') |
|
1858 ->fields('t', array('name')); |
|
1859 // Subquery to {test_people}. |
|
1860 $subquery = db_select('test_people', 'tp') |
|
1861 ->fields('tp', array('name')) |
|
1862 ->where('tp.name = t.name'); |
|
1863 $query->exists($subquery); |
|
1864 $result = $query->execute(); |
|
1865 |
|
1866 // Ensure that we got the right record. |
|
1867 $record = $result->fetch(); |
|
1868 $this->assertEqual($record->name, 'George', 'Fetched name is correct using EXISTS query.'); |
|
1869 } |
|
1870 |
|
1871 /** |
|
1872 * Test NOT EXISTS subquery conditionals on SELECT statements. |
|
1873 * |
|
1874 * We essentially select all rows from the {test} table that don't have |
|
1875 * matching rows in the {test_people} table based on the shared name column. |
|
1876 */ |
|
1877 function testNotExistsSubquerySelect() { |
|
1878 // Put George into {test_people}. |
|
1879 db_insert('test_people') |
|
1880 ->fields(array( |
|
1881 'name' => 'George', |
|
1882 'age' => 27, |
|
1883 'job' => 'Singer', |
|
1884 )) |
|
1885 ->execute(); |
|
1886 |
|
1887 // Base query to {test}. |
|
1888 $query = db_select('test', 't') |
|
1889 ->fields('t', array('name')); |
|
1890 // Subquery to {test_people}. |
|
1891 $subquery = db_select('test_people', 'tp') |
|
1892 ->fields('tp', array('name')) |
|
1893 ->where('tp.name = t.name'); |
|
1894 $query->notExists($subquery); |
|
1895 |
|
1896 // Ensure that we got the right number of records. |
|
1897 $people = $query->execute()->fetchCol(); |
|
1898 $this->assertEqual(count($people), 3, 'NOT EXISTS query returned the correct results.'); |
|
1899 } |
|
1900 } |
|
1901 |
|
1902 /** |
|
1903 * Test select with order by clauses. |
|
1904 */ |
|
1905 class DatabaseSelectOrderedTestCase extends DatabaseTestCase { |
|
1906 |
|
1907 public static function getInfo() { |
|
1908 return array( |
|
1909 'name' => 'Select tests, ordered', |
|
1910 'description' => 'Test the Select query builder.', |
|
1911 'group' => 'Database', |
|
1912 ); |
|
1913 } |
|
1914 |
|
1915 /** |
|
1916 * Test basic order by. |
|
1917 */ |
|
1918 function testSimpleSelectOrdered() { |
|
1919 $query = db_select('test'); |
|
1920 $name_field = $query->addField('test', 'name'); |
|
1921 $age_field = $query->addField('test', 'age', 'age'); |
|
1922 $query->orderBy($age_field); |
|
1923 $result = $query->execute(); |
|
1924 |
|
1925 $num_records = 0; |
|
1926 $last_age = 0; |
|
1927 foreach ($result as $record) { |
|
1928 $num_records++; |
|
1929 $this->assertTrue($record->age >= $last_age, 'Results returned in correct order.'); |
|
1930 $last_age = $record->age; |
|
1931 } |
|
1932 |
|
1933 $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); |
|
1934 } |
|
1935 |
|
1936 /** |
|
1937 * Test multiple order by. |
|
1938 */ |
|
1939 function testSimpleSelectMultiOrdered() { |
|
1940 $query = db_select('test'); |
|
1941 $name_field = $query->addField('test', 'name'); |
|
1942 $age_field = $query->addField('test', 'age', 'age'); |
|
1943 $job_field = $query->addField('test', 'job'); |
|
1944 $query->orderBy($job_field); |
|
1945 $query->orderBy($age_field); |
|
1946 $result = $query->execute(); |
|
1947 |
|
1948 $num_records = 0; |
|
1949 $expected = array( |
|
1950 array('Ringo', 28, 'Drummer'), |
|
1951 array('John', 25, 'Singer'), |
|
1952 array('George', 27, 'Singer'), |
|
1953 array('Paul', 26, 'Songwriter'), |
|
1954 ); |
|
1955 $results = $result->fetchAll(PDO::FETCH_NUM); |
|
1956 foreach ($expected as $k => $record) { |
|
1957 $num_records++; |
|
1958 foreach ($record as $kk => $col) { |
|
1959 if ($expected[$k][$kk] != $results[$k][$kk]) { |
|
1960 $this->assertTrue(FALSE, 'Results returned in correct order.'); |
|
1961 } |
|
1962 } |
|
1963 } |
|
1964 $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); |
|
1965 } |
|
1966 |
|
1967 /** |
|
1968 * Test order by descending. |
|
1969 */ |
|
1970 function testSimpleSelectOrderedDesc() { |
|
1971 $query = db_select('test'); |
|
1972 $name_field = $query->addField('test', 'name'); |
|
1973 $age_field = $query->addField('test', 'age', 'age'); |
|
1974 $query->orderBy($age_field, 'DESC'); |
|
1975 $result = $query->execute(); |
|
1976 |
|
1977 $num_records = 0; |
|
1978 $last_age = 100000000; |
|
1979 foreach ($result as $record) { |
|
1980 $num_records++; |
|
1981 $this->assertTrue($record->age <= $last_age, 'Results returned in correct order.'); |
|
1982 $last_age = $record->age; |
|
1983 } |
|
1984 |
|
1985 $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); |
|
1986 } |
|
1987 |
|
1988 /** |
|
1989 * Tests that the sort direction is sanitized properly. |
|
1990 */ |
|
1991 function testOrderByEscaping() { |
|
1992 $query = db_select('test')->orderBy('name', 'invalid direction'); |
|
1993 $order_bys = $query->getOrderBy(); |
|
1994 $this->assertEqual($order_bys['name'], 'ASC', 'Invalid order by direction is converted to ASC.'); |
|
1995 } |
|
1996 } |
|
1997 |
|
1998 /** |
|
1999 * Test more complex select statements. |
|
2000 */ |
|
2001 class DatabaseSelectComplexTestCase extends DatabaseTestCase { |
|
2002 |
|
2003 public static function getInfo() { |
|
2004 return array( |
|
2005 'name' => 'Select tests, complex', |
|
2006 'description' => 'Test the Select query builder with more complex queries.', |
|
2007 'group' => 'Database', |
|
2008 ); |
|
2009 } |
|
2010 |
|
2011 /** |
|
2012 * Test simple JOIN statements. |
|
2013 */ |
|
2014 function testDefaultJoin() { |
|
2015 $query = db_select('test_task', 't'); |
|
2016 $people_alias = $query->join('test', 'p', 't.pid = p.id'); |
|
2017 $name_field = $query->addField($people_alias, 'name', 'name'); |
|
2018 $task_field = $query->addField('t', 'task', 'task'); |
|
2019 $priority_field = $query->addField('t', 'priority', 'priority'); |
|
2020 |
|
2021 $query->orderBy($priority_field); |
|
2022 $result = $query->execute(); |
|
2023 |
|
2024 $num_records = 0; |
|
2025 $last_priority = 0; |
|
2026 foreach ($result as $record) { |
|
2027 $num_records++; |
|
2028 $this->assertTrue($record->$priority_field >= $last_priority, 'Results returned in correct order.'); |
|
2029 $this->assertNotEqual($record->$name_field, 'Ringo', 'Taskless person not selected.'); |
|
2030 $last_priority = $record->$priority_field; |
|
2031 } |
|
2032 |
|
2033 $this->assertEqual($num_records, 7, 'Returned the correct number of rows.'); |
|
2034 } |
|
2035 |
|
2036 /** |
|
2037 * Test LEFT OUTER joins. |
|
2038 */ |
|
2039 function testLeftOuterJoin() { |
|
2040 $query = db_select('test', 'p'); |
|
2041 $people_alias = $query->leftJoin('test_task', 't', 't.pid = p.id'); |
|
2042 $name_field = $query->addField('p', 'name', 'name'); |
|
2043 $task_field = $query->addField($people_alias, 'task', 'task'); |
|
2044 $priority_field = $query->addField($people_alias, 'priority', 'priority'); |
|
2045 |
|
2046 $query->orderBy($name_field); |
|
2047 $result = $query->execute(); |
|
2048 |
|
2049 $num_records = 0; |
|
2050 $last_name = 0; |
|
2051 |
|
2052 foreach ($result as $record) { |
|
2053 $num_records++; |
|
2054 $this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, 'Results returned in correct order.'); |
|
2055 $last_priority = $record->$name_field; |
|
2056 } |
|
2057 |
|
2058 $this->assertEqual($num_records, 8, 'Returned the correct number of rows.'); |
|
2059 } |
|
2060 |
|
2061 /** |
|
2062 * Test GROUP BY clauses. |
|
2063 */ |
|
2064 function testGroupBy() { |
|
2065 $query = db_select('test_task', 't'); |
|
2066 $count_field = $query->addExpression('COUNT(task)', 'num'); |
|
2067 $task_field = $query->addField('t', 'task'); |
|
2068 $query->orderBy($count_field); |
|
2069 $query->groupBy($task_field); |
|
2070 $result = $query->execute(); |
|
2071 |
|
2072 $num_records = 0; |
|
2073 $last_count = 0; |
|
2074 $records = array(); |
|
2075 foreach ($result as $record) { |
|
2076 $num_records++; |
|
2077 $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.'); |
|
2078 $last_count = $record->$count_field; |
|
2079 $records[$record->$task_field] = $record->$count_field; |
|
2080 } |
|
2081 |
|
2082 $correct_results = array( |
|
2083 'eat' => 1, |
|
2084 'sleep' => 2, |
|
2085 'code' => 1, |
|
2086 'found new band' => 1, |
|
2087 'perform at superbowl' => 1, |
|
2088 ); |
|
2089 |
|
2090 foreach ($correct_results as $task => $count) { |
|
2091 $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task))); |
|
2092 } |
|
2093 |
|
2094 $this->assertEqual($num_records, 6, 'Returned the correct number of total rows.'); |
|
2095 } |
|
2096 |
|
2097 /** |
|
2098 * Test GROUP BY and HAVING clauses together. |
|
2099 */ |
|
2100 function testGroupByAndHaving() { |
|
2101 $query = db_select('test_task', 't'); |
|
2102 $count_field = $query->addExpression('COUNT(task)', 'num'); |
|
2103 $task_field = $query->addField('t', 'task'); |
|
2104 $query->orderBy($count_field); |
|
2105 $query->groupBy($task_field); |
|
2106 $query->having('COUNT(task) >= 2'); |
|
2107 $result = $query->execute(); |
|
2108 |
|
2109 $num_records = 0; |
|
2110 $last_count = 0; |
|
2111 $records = array(); |
|
2112 foreach ($result as $record) { |
|
2113 $num_records++; |
|
2114 $this->assertTrue($record->$count_field >= 2, 'Record has the minimum count.'); |
|
2115 $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.'); |
|
2116 $last_count = $record->$count_field; |
|
2117 $records[$record->$task_field] = $record->$count_field; |
|
2118 } |
|
2119 |
|
2120 $correct_results = array( |
|
2121 'sleep' => 2, |
|
2122 ); |
|
2123 |
|
2124 foreach ($correct_results as $task => $count) { |
|
2125 $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task))); |
|
2126 } |
|
2127 |
|
2128 $this->assertEqual($num_records, 1, 'Returned the correct number of total rows.'); |
|
2129 } |
|
2130 |
|
2131 /** |
|
2132 * Test range queries. The SQL clause varies with the database. |
|
2133 */ |
|
2134 function testRange() { |
|
2135 $query = db_select('test'); |
|
2136 $name_field = $query->addField('test', 'name'); |
|
2137 $age_field = $query->addField('test', 'age', 'age'); |
|
2138 $query->range(0, 2); |
|
2139 $result = $query->execute(); |
|
2140 |
|
2141 $num_records = 0; |
|
2142 foreach ($result as $record) { |
|
2143 $num_records++; |
|
2144 } |
|
2145 |
|
2146 $this->assertEqual($num_records, 2, 'Returned the correct number of rows.'); |
|
2147 } |
|
2148 |
|
2149 /** |
|
2150 * Test distinct queries. |
|
2151 */ |
|
2152 function testDistinct() { |
|
2153 $query = db_select('test_task'); |
|
2154 $task_field = $query->addField('test_task', 'task'); |
|
2155 $query->distinct(); |
|
2156 $result = $query->execute(); |
|
2157 |
|
2158 $num_records = 0; |
|
2159 foreach ($result as $record) { |
|
2160 $num_records++; |
|
2161 } |
|
2162 |
|
2163 $this->assertEqual($num_records, 6, 'Returned the correct number of rows.'); |
|
2164 } |
|
2165 |
|
2166 /** |
|
2167 * Test that we can generate a count query from a built query. |
|
2168 */ |
|
2169 function testCountQuery() { |
|
2170 $query = db_select('test'); |
|
2171 $name_field = $query->addField('test', 'name'); |
|
2172 $age_field = $query->addField('test', 'age', 'age'); |
|
2173 $query->orderBy('name'); |
|
2174 |
|
2175 $count = $query->countQuery()->execute()->fetchField(); |
|
2176 |
|
2177 $this->assertEqual($count, 4, 'Counted the correct number of records.'); |
|
2178 |
|
2179 // Now make sure we didn't break the original query! We should still have |
|
2180 // all of the fields we asked for. |
|
2181 $record = $query->execute()->fetch(); |
|
2182 $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.'); |
|
2183 $this->assertEqual($record->$age_field, 27, 'Correct data retrieved.'); |
|
2184 } |
|
2185 |
|
2186 function testHavingCountQuery() { |
|
2187 $query = db_select('test') |
|
2188 ->extend('PagerDefault') |
|
2189 ->groupBy('age') |
|
2190 ->having('age + 1 > 0'); |
|
2191 $query->addField('test', 'age'); |
|
2192 $query->addExpression('age + 1'); |
|
2193 $count = count($query->execute()->fetchCol()); |
|
2194 $this->assertEqual($count, 4, 'Counted the correct number of records.'); |
|
2195 } |
|
2196 |
|
2197 /** |
|
2198 * Test that countQuery properly removes 'all_fields' statements and |
|
2199 * ordering clauses. |
|
2200 */ |
|
2201 function testCountQueryRemovals() { |
|
2202 $query = db_select('test'); |
|
2203 $query->fields('test'); |
|
2204 $query->orderBy('name'); |
|
2205 $count = $query->countQuery(); |
|
2206 |
|
2207 // Check that the 'all_fields' statement is handled properly. |
|
2208 $tables = $query->getTables(); |
|
2209 $this->assertEqual($tables['test']['all_fields'], 1, 'Query correctly sets \'all_fields\' statement.'); |
|
2210 $tables = $count->getTables(); |
|
2211 $this->assertFalse(isset($tables['test']['all_fields']), 'Count query correctly unsets \'all_fields\' statement.'); |
|
2212 |
|
2213 // Check that the ordering clause is handled properly. |
|
2214 $orderby = $query->getOrderBy(); |
|
2215 $this->assertEqual($orderby['name'], 'ASC', 'Query correctly sets ordering clause.'); |
|
2216 $orderby = $count->getOrderBy(); |
|
2217 $this->assertFalse(isset($orderby['name']), 'Count query correctly unsets ordering caluse.'); |
|
2218 |
|
2219 // Make sure that the count query works. |
|
2220 $count = $count->execute()->fetchField(); |
|
2221 |
|
2222 $this->assertEqual($count, 4, 'Counted the correct number of records.'); |
|
2223 } |
|
2224 |
|
2225 |
|
2226 /** |
|
2227 * Test that countQuery properly removes fields and expressions. |
|
2228 */ |
|
2229 function testCountQueryFieldRemovals() { |
|
2230 // countQuery should remove all fields and expressions, so this can be |
|
2231 // tested by adding a non-existent field and expression: if it ends |
|
2232 // up in the query, an error will be thrown. If not, it will return the |
|
2233 // number of records, which in this case happens to be 4 (there are four |
|
2234 // records in the {test} table). |
|
2235 $query = db_select('test'); |
|
2236 $query->fields('test', array('fail')); |
|
2237 $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed fields'); |
|
2238 |
|
2239 $query = db_select('test'); |
|
2240 $query->addExpression('fail'); |
|
2241 $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed expressions'); |
|
2242 } |
|
2243 |
|
2244 /** |
|
2245 * Test that we can generate a count query from a query with distinct. |
|
2246 */ |
|
2247 function testCountQueryDistinct() { |
|
2248 $query = db_select('test_task'); |
|
2249 $task_field = $query->addField('test_task', 'task'); |
|
2250 $query->distinct(); |
|
2251 |
|
2252 $count = $query->countQuery()->execute()->fetchField(); |
|
2253 |
|
2254 $this->assertEqual($count, 6, 'Counted the correct number of records.'); |
|
2255 } |
|
2256 |
|
2257 /** |
|
2258 * Test that we can generate a count query from a query with GROUP BY. |
|
2259 */ |
|
2260 function testCountQueryGroupBy() { |
|
2261 $query = db_select('test_task'); |
|
2262 $pid_field = $query->addField('test_task', 'pid'); |
|
2263 $query->groupBy('pid'); |
|
2264 |
|
2265 $count = $query->countQuery()->execute()->fetchField(); |
|
2266 |
|
2267 $this->assertEqual($count, 3, 'Counted the correct number of records.'); |
|
2268 |
|
2269 // Use a column alias as, without one, the query can succeed for the wrong |
|
2270 // reason. |
|
2271 $query = db_select('test_task'); |
|
2272 $pid_field = $query->addField('test_task', 'pid', 'pid_alias'); |
|
2273 $query->addExpression('COUNT(test_task.task)', 'count'); |
|
2274 $query->groupBy('pid_alias'); |
|
2275 $query->orderBy('pid_alias', 'asc'); |
|
2276 |
|
2277 $count = $query->countQuery()->execute()->fetchField(); |
|
2278 |
|
2279 $this->assertEqual($count, 3, 'Counted the correct number of records.'); |
|
2280 } |
|
2281 |
|
2282 /** |
|
2283 * Confirm that we can properly nest conditional clauses. |
|
2284 */ |
|
2285 function testNestedConditions() { |
|
2286 // This query should translate to: |
|
2287 // "SELECT job FROM {test} WHERE name = 'Paul' AND (age = 26 OR age = 27)" |
|
2288 // That should find only one record. Yes it's a non-optimal way of writing |
|
2289 // that query but that's not the point! |
|
2290 $query = db_select('test'); |
|
2291 $query->addField('test', 'job'); |
|
2292 $query->condition('name', 'Paul'); |
|
2293 $query->condition(db_or()->condition('age', 26)->condition('age', 27)); |
|
2294 |
|
2295 $job = $query->execute()->fetchField(); |
|
2296 $this->assertEqual($job, 'Songwriter', 'Correct data retrieved.'); |
|
2297 } |
|
2298 |
|
2299 /** |
|
2300 * Confirm we can join on a single table twice with a dynamic alias. |
|
2301 */ |
|
2302 function testJoinTwice() { |
|
2303 $query = db_select('test')->fields('test'); |
|
2304 $alias = $query->join('test', 'test', 'test.job = %alias.job'); |
|
2305 $query->addField($alias, 'name', 'othername'); |
|
2306 $query->addField($alias, 'job', 'otherjob'); |
|
2307 $query->where("$alias.name <> test.name"); |
|
2308 $crowded_job = $query->execute()->fetch(); |
|
2309 $this->assertEqual($crowded_job->job, $crowded_job->otherjob, 'Correctly joined same table twice.'); |
|
2310 $this->assertNotEqual($crowded_job->name, $crowded_job->othername, 'Correctly joined same table twice.'); |
|
2311 } |
|
2312 |
|
2313 } |
|
2314 |
|
2315 /** |
|
2316 * Test more complex select statements, part 2. |
|
2317 */ |
|
2318 class DatabaseSelectComplexTestCase2 extends DatabaseTestCase { |
|
2319 |
|
2320 public static function getInfo() { |
|
2321 return array( |
|
2322 'name' => 'Select tests, complex 2', |
|
2323 'description' => 'Test the Select query builder with even more complex queries.', |
|
2324 'group' => 'Database', |
|
2325 ); |
|
2326 } |
|
2327 |
|
2328 function setUp() { |
|
2329 DrupalWebTestCase::setUp('database_test', 'node_access_test'); |
|
2330 |
|
2331 $schema['test'] = drupal_get_schema('test'); |
|
2332 $schema['test_people'] = drupal_get_schema('test_people'); |
|
2333 $schema['test_one_blob'] = drupal_get_schema('test_one_blob'); |
|
2334 $schema['test_two_blobs'] = drupal_get_schema('test_two_blobs'); |
|
2335 $schema['test_task'] = drupal_get_schema('test_task'); |
|
2336 |
|
2337 $this->installTables($schema); |
|
2338 |
|
2339 $this->addSampleData(); |
|
2340 } |
|
2341 |
|
2342 /** |
|
2343 * Test that we can join on a query. |
|
2344 */ |
|
2345 function testJoinSubquery() { |
|
2346 $acct = $this->drupalCreateUser(array('access content')); |
|
2347 $this->drupalLogin($acct); |
|
2348 |
|
2349 $query = db_select('test_task', 'tt', array('target' => 'slave')); |
|
2350 $query->addExpression('tt.pid + 1', 'abc'); |
|
2351 $query->condition('priority', 1, '>'); |
|
2352 $query->condition('priority', 100, '<'); |
|
2353 |
|
2354 $subquery = db_select('test', 'tp'); |
|
2355 $subquery->join('test_one_blob', 'tpb', 'tp.id = tpb.id'); |
|
2356 $subquery->join('node', 'n', 'tp.id = n.nid'); |
|
2357 $subquery->addTag('node_access'); |
|
2358 $subquery->addMetaData('account', $acct); |
|
2359 $subquery->addField('tp', 'id'); |
|
2360 $subquery->condition('age', 5, '>'); |
|
2361 $subquery->condition('age', 500, '<'); |
|
2362 |
|
2363 $query->leftJoin($subquery, 'sq', 'tt.pid = sq.id'); |
|
2364 $query->join('test_one_blob', 'tb3', 'tt.pid = tb3.id'); |
|
2365 |
|
2366 // Construct the query string. |
|
2367 // This is the same sequence that SelectQuery::execute() goes through. |
|
2368 $query->preExecute(); |
|
2369 $query->getArguments(); |
|
2370 $str = (string) $query; |
|
2371 |
|
2372 // Verify that the string only has one copy of condition placeholder 0. |
|
2373 $pos = strpos($str, 'db_condition_placeholder_0', 0); |
|
2374 $pos2 = strpos($str, 'db_condition_placeholder_0', $pos + 1); |
|
2375 $this->assertFalse($pos2, 'Condition placeholder is not repeated.'); |
|
2376 } |
|
2377 } |
|
2378 |
|
2379 class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { |
|
2380 |
|
2381 public static function getInfo() { |
|
2382 return array( |
|
2383 'name' => 'Pager query tests', |
|
2384 'description' => 'Test the pager query extender.', |
|
2385 'group' => 'Database', |
|
2386 ); |
|
2387 } |
|
2388 |
|
2389 /** |
|
2390 * Confirm that a pager query returns the correct results. |
|
2391 * |
|
2392 * Note that we have to make an HTTP request to a test page handler |
|
2393 * because the pager depends on GET parameters. |
|
2394 */ |
|
2395 function testEvenPagerQuery() { |
|
2396 // To keep the test from being too brittle, we determine up front |
|
2397 // what the page count should be dynamically, and pass the control |
|
2398 // information forward to the actual query on the other side of the |
|
2399 // HTTP request. |
|
2400 $limit = 2; |
|
2401 $count = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
2402 |
|
2403 $correct_number = $limit; |
|
2404 $num_pages = floor($count / $limit); |
|
2405 |
|
2406 // If there is no remainder from rounding, subtract 1 since we index from 0. |
|
2407 if (!($num_pages * $limit < $count)) { |
|
2408 $num_pages--; |
|
2409 } |
|
2410 |
|
2411 for ($page = 0; $page <= $num_pages; ++$page) { |
|
2412 $this->drupalGet('database_test/pager_query_even/' . $limit, array('query' => array('page' => $page))); |
|
2413 $data = json_decode($this->drupalGetContent()); |
|
2414 |
|
2415 if ($page == $num_pages) { |
|
2416 $correct_number = $count - ($limit * $page); |
|
2417 } |
|
2418 |
|
2419 $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number))); |
|
2420 } |
|
2421 } |
|
2422 |
|
2423 /** |
|
2424 * Confirm that a pager query returns the correct results. |
|
2425 * |
|
2426 * Note that we have to make an HTTP request to a test page handler |
|
2427 * because the pager depends on GET parameters. |
|
2428 */ |
|
2429 function testOddPagerQuery() { |
|
2430 // To keep the test from being too brittle, we determine up front |
|
2431 // what the page count should be dynamically, and pass the control |
|
2432 // information forward to the actual query on the other side of the |
|
2433 // HTTP request. |
|
2434 $limit = 2; |
|
2435 $count = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField(); |
|
2436 |
|
2437 $correct_number = $limit; |
|
2438 $num_pages = floor($count / $limit); |
|
2439 |
|
2440 // If there is no remainder from rounding, subtract 1 since we index from 0. |
|
2441 if (!($num_pages * $limit < $count)) { |
|
2442 $num_pages--; |
|
2443 } |
|
2444 |
|
2445 for ($page = 0; $page <= $num_pages; ++$page) { |
|
2446 $this->drupalGet('database_test/pager_query_odd/' . $limit, array('query' => array('page' => $page))); |
|
2447 $data = json_decode($this->drupalGetContent()); |
|
2448 |
|
2449 if ($page == $num_pages) { |
|
2450 $correct_number = $count - ($limit * $page); |
|
2451 } |
|
2452 |
|
2453 $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number))); |
|
2454 } |
|
2455 } |
|
2456 |
|
2457 /** |
|
2458 * Confirm that a pager query with inner pager query returns valid results. |
|
2459 * |
|
2460 * This is a regression test for #467984. |
|
2461 */ |
|
2462 function testInnerPagerQuery() { |
|
2463 $query = db_select('test', 't')->extend('PagerDefault'); |
|
2464 $query |
|
2465 ->fields('t', array('age')) |
|
2466 ->orderBy('age') |
|
2467 ->limit(5); |
|
2468 |
|
2469 $outer_query = db_select($query); |
|
2470 $outer_query->addField('subquery', 'age'); |
|
2471 |
|
2472 $ages = $outer_query |
|
2473 ->execute() |
|
2474 ->fetchCol(); |
|
2475 $this->assertEqual($ages, array(25, 26, 27, 28), 'Inner pager query returned the correct ages.'); |
|
2476 } |
|
2477 |
|
2478 /** |
|
2479 * Confirm that a paging query with a having expression returns valid results. |
|
2480 * |
|
2481 * This is a regression test for #467984. |
|
2482 */ |
|
2483 function testHavingPagerQuery() { |
|
2484 $query = db_select('test', 't')->extend('PagerDefault'); |
|
2485 $query |
|
2486 ->fields('t', array('name')) |
|
2487 ->orderBy('name') |
|
2488 ->groupBy('name') |
|
2489 ->having('MAX(age) > :count', array(':count' => 26)) |
|
2490 ->limit(5); |
|
2491 |
|
2492 $ages = $query |
|
2493 ->execute() |
|
2494 ->fetchCol(); |
|
2495 $this->assertEqual($ages, array('George', 'Ringo'), 'Pager query with having expression returned the correct ages.'); |
|
2496 } |
|
2497 |
|
2498 /** |
|
2499 * Confirm that every pager gets a valid non-overlaping element ID. |
|
2500 */ |
|
2501 function testElementNumbers() { |
|
2502 $_GET['page'] = '3, 2, 1, 0'; |
|
2503 |
|
2504 $name = db_select('test', 't')->extend('PagerDefault') |
|
2505 ->element(2) |
|
2506 ->fields('t', array('name')) |
|
2507 ->orderBy('age') |
|
2508 ->limit(1) |
|
2509 ->execute() |
|
2510 ->fetchField(); |
|
2511 $this->assertEqual($name, 'Paul', 'Pager query #1 with a specified element ID returned the correct results.'); |
|
2512 |
|
2513 // Setting an element smaller than the previous one |
|
2514 // should not overwrite the pager $maxElement with a smaller value. |
|
2515 $name = db_select('test', 't')->extend('PagerDefault') |
|
2516 ->element(1) |
|
2517 ->fields('t', array('name')) |
|
2518 ->orderBy('age') |
|
2519 ->limit(1) |
|
2520 ->execute() |
|
2521 ->fetchField(); |
|
2522 $this->assertEqual($name, 'George', 'Pager query #2 with a specified element ID returned the correct results.'); |
|
2523 |
|
2524 $name = db_select('test', 't')->extend('PagerDefault') |
|
2525 ->fields('t', array('name')) |
|
2526 ->orderBy('age') |
|
2527 ->limit(1) |
|
2528 ->execute() |
|
2529 ->fetchField(); |
|
2530 $this->assertEqual($name, 'John', 'Pager query #3 with a generated element ID returned the correct results.'); |
|
2531 |
|
2532 unset($_GET['page']); |
|
2533 } |
|
2534 } |
|
2535 |
|
2536 |
|
2537 class DatabaseSelectTableSortDefaultTestCase extends DatabaseTestCase { |
|
2538 |
|
2539 public static function getInfo() { |
|
2540 return array( |
|
2541 'name' => 'Tablesort query tests', |
|
2542 'description' => 'Test the tablesort query extender.', |
|
2543 'group' => 'Database', |
|
2544 ); |
|
2545 } |
|
2546 |
|
2547 /** |
|
2548 * Confirm that a tablesort query returns the correct results. |
|
2549 * |
|
2550 * Note that we have to make an HTTP request to a test page handler |
|
2551 * because the pager depends on GET parameters. |
|
2552 */ |
|
2553 function testTableSortQuery() { |
|
2554 $sorts = array( |
|
2555 array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'), |
|
2556 array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'), |
|
2557 array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'), |
|
2558 array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'), |
|
2559 // more elements here |
|
2560 |
|
2561 ); |
|
2562 |
|
2563 foreach ($sorts as $sort) { |
|
2564 $this->drupalGet('database_test/tablesort/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort']))); |
|
2565 $data = json_decode($this->drupalGetContent()); |
|
2566 |
|
2567 $first = array_shift($data->tasks); |
|
2568 $last = array_pop($data->tasks); |
|
2569 |
|
2570 $this->assertEqual($first->task, $sort['first'], 'Items appear in the correct order.'); |
|
2571 $this->assertEqual($last->task, $sort['last'], 'Items appear in the correct order.'); |
|
2572 } |
|
2573 } |
|
2574 |
|
2575 /** |
|
2576 * Confirm that if a tablesort's orderByHeader is called before another orderBy, that the header happens first. |
|
2577 * |
|
2578 */ |
|
2579 function testTableSortQueryFirst() { |
|
2580 $sorts = array( |
|
2581 array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'), |
|
2582 array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'), |
|
2583 array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'), |
|
2584 array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'), |
|
2585 // more elements here |
|
2586 |
|
2587 ); |
|
2588 |
|
2589 foreach ($sorts as $sort) { |
|
2590 $this->drupalGet('database_test/tablesort_first/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort']))); |
|
2591 $data = json_decode($this->drupalGetContent()); |
|
2592 |
|
2593 $first = array_shift($data->tasks); |
|
2594 $last = array_pop($data->tasks); |
|
2595 |
|
2596 $this->assertEqual($first->task, $sort['first'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort']))); |
|
2597 $this->assertEqual($last->task, $sort['last'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort']))); |
|
2598 } |
|
2599 } |
|
2600 |
|
2601 /** |
|
2602 * Confirm that if a sort is not set in a tableselect form there is no error thrown when using the default. |
|
2603 */ |
|
2604 function testTableSortDefaultSort() { |
|
2605 $this->drupalGet('database_test/tablesort_default_sort'); |
|
2606 // Any PHP errors or notices thrown would trigger a simpletest exception, so |
|
2607 // no additional assertions are needed. |
|
2608 } |
|
2609 } |
|
2610 |
|
2611 /** |
|
2612 * Select tagging tests. |
|
2613 * |
|
2614 * Tags are a way to flag queries for alter hooks so they know |
|
2615 * what type of query it is, such as "node_access". |
|
2616 */ |
|
2617 class DatabaseTaggingTestCase extends DatabaseTestCase { |
|
2618 |
|
2619 public static function getInfo() { |
|
2620 return array( |
|
2621 'name' => 'Query tagging tests', |
|
2622 'description' => 'Test the tagging capabilities of the Select builder.', |
|
2623 'group' => 'Database', |
|
2624 ); |
|
2625 } |
|
2626 |
|
2627 /** |
|
2628 * Confirm that a query has a "tag" added to it. |
|
2629 */ |
|
2630 function testHasTag() { |
|
2631 $query = db_select('test'); |
|
2632 $query->addField('test', 'name'); |
|
2633 $query->addField('test', 'age', 'age'); |
|
2634 |
|
2635 $query->addTag('test'); |
|
2636 |
|
2637 $this->assertTrue($query->hasTag('test'), 'hasTag() returned true.'); |
|
2638 $this->assertFalse($query->hasTag('other'), 'hasTag() returned false.'); |
|
2639 } |
|
2640 |
|
2641 /** |
|
2642 * Test query tagging "has all of these tags" functionality. |
|
2643 */ |
|
2644 function testHasAllTags() { |
|
2645 $query = db_select('test'); |
|
2646 $query->addField('test', 'name'); |
|
2647 $query->addField('test', 'age', 'age'); |
|
2648 |
|
2649 $query->addTag('test'); |
|
2650 $query->addTag('other'); |
|
2651 |
|
2652 $this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.'); |
|
2653 $this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.'); |
|
2654 } |
|
2655 |
|
2656 /** |
|
2657 * Test query tagging "has at least one of these tags" functionality. |
|
2658 */ |
|
2659 function testHasAnyTag() { |
|
2660 $query = db_select('test'); |
|
2661 $query->addField('test', 'name'); |
|
2662 $query->addField('test', 'age', 'age'); |
|
2663 |
|
2664 $query->addTag('test'); |
|
2665 |
|
2666 $this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.'); |
|
2667 $this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.'); |
|
2668 } |
|
2669 |
|
2670 /** |
|
2671 * Confirm that an extended query has a "tag" added to it. |
|
2672 */ |
|
2673 function testExtenderHasTag() { |
|
2674 $query = db_select('test') |
|
2675 ->extend('SelectQueryExtender'); |
|
2676 $query->addField('test', 'name'); |
|
2677 $query->addField('test', 'age', 'age'); |
|
2678 |
|
2679 $query->addTag('test'); |
|
2680 |
|
2681 $this->assertTrue($query->hasTag('test'), 'hasTag() returned true.'); |
|
2682 $this->assertFalse($query->hasTag('other'), 'hasTag() returned false.'); |
|
2683 } |
|
2684 |
|
2685 /** |
|
2686 * Test extended query tagging "has all of these tags" functionality. |
|
2687 */ |
|
2688 function testExtenderHasAllTags() { |
|
2689 $query = db_select('test') |
|
2690 ->extend('SelectQueryExtender'); |
|
2691 $query->addField('test', 'name'); |
|
2692 $query->addField('test', 'age', 'age'); |
|
2693 |
|
2694 $query->addTag('test'); |
|
2695 $query->addTag('other'); |
|
2696 |
|
2697 $this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.'); |
|
2698 $this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.'); |
|
2699 } |
|
2700 |
|
2701 /** |
|
2702 * Test extended query tagging "has at least one of these tags" functionality. |
|
2703 */ |
|
2704 function testExtenderHasAnyTag() { |
|
2705 $query = db_select('test') |
|
2706 ->extend('SelectQueryExtender'); |
|
2707 $query->addField('test', 'name'); |
|
2708 $query->addField('test', 'age', 'age'); |
|
2709 |
|
2710 $query->addTag('test'); |
|
2711 |
|
2712 $this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.'); |
|
2713 $this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.'); |
|
2714 } |
|
2715 |
|
2716 /** |
|
2717 * Test that we can attach meta data to a query object. |
|
2718 * |
|
2719 * This is how we pass additional context to alter hooks. |
|
2720 */ |
|
2721 function testMetaData() { |
|
2722 $query = db_select('test'); |
|
2723 $query->addField('test', 'name'); |
|
2724 $query->addField('test', 'age', 'age'); |
|
2725 |
|
2726 $data = array( |
|
2727 'a' => 'A', |
|
2728 'b' => 'B', |
|
2729 ); |
|
2730 |
|
2731 $query->addMetaData('test', $data); |
|
2732 |
|
2733 $return = $query->getMetaData('test'); |
|
2734 $this->assertEqual($data, $return, 'Corect metadata returned.'); |
|
2735 |
|
2736 $return = $query->getMetaData('nothere'); |
|
2737 $this->assertNull($return, 'Non-existent key returned NULL.'); |
|
2738 } |
|
2739 } |
|
2740 |
|
2741 /** |
|
2742 * Select alter tests. |
|
2743 * |
|
2744 * @see database_test_query_alter() |
|
2745 */ |
|
2746 class DatabaseAlterTestCase extends DatabaseTestCase { |
|
2747 |
|
2748 public static function getInfo() { |
|
2749 return array( |
|
2750 'name' => 'Query altering tests', |
|
2751 'description' => 'Test the hook_query_alter capabilities of the Select builder.', |
|
2752 'group' => 'Database', |
|
2753 ); |
|
2754 } |
|
2755 |
|
2756 /** |
|
2757 * Test that we can do basic alters. |
|
2758 */ |
|
2759 function testSimpleAlter() { |
|
2760 $query = db_select('test'); |
|
2761 $query->addField('test', 'name'); |
|
2762 $query->addField('test', 'age', 'age'); |
|
2763 $query->addTag('database_test_alter_add_range'); |
|
2764 |
|
2765 $result = $query->execute(); |
|
2766 |
|
2767 $num_records = 0; |
|
2768 foreach ($result as $record) { |
|
2769 $num_records++; |
|
2770 } |
|
2771 |
|
2772 $this->assertEqual($num_records, 2, 'Returned the correct number of rows.'); |
|
2773 } |
|
2774 |
|
2775 /** |
|
2776 * Test that we can alter the joins on a query. |
|
2777 */ |
|
2778 function testAlterWithJoin() { |
|
2779 $query = db_select('test_task'); |
|
2780 $tid_field = $query->addField('test_task', 'tid'); |
|
2781 $task_field = $query->addField('test_task', 'task'); |
|
2782 $query->orderBy($task_field); |
|
2783 $query->addTag('database_test_alter_add_join'); |
|
2784 |
|
2785 $result = $query->execute(); |
|
2786 |
|
2787 $records = $result->fetchAll(); |
|
2788 |
|
2789 $this->assertEqual(count($records), 2, 'Returned the correct number of rows.'); |
|
2790 |
|
2791 $this->assertEqual($records[0]->name, 'George', 'Correct data retrieved.'); |
|
2792 $this->assertEqual($records[0]->$tid_field, 4, 'Correct data retrieved.'); |
|
2793 $this->assertEqual($records[0]->$task_field, 'sing', 'Correct data retrieved.'); |
|
2794 $this->assertEqual($records[1]->name, 'George', 'Correct data retrieved.'); |
|
2795 $this->assertEqual($records[1]->$tid_field, 5, 'Correct data retrieved.'); |
|
2796 $this->assertEqual($records[1]->$task_field, 'sleep', 'Correct data retrieved.'); |
|
2797 } |
|
2798 |
|
2799 /** |
|
2800 * Test that we can alter a query's conditionals. |
|
2801 */ |
|
2802 function testAlterChangeConditional() { |
|
2803 $query = db_select('test_task'); |
|
2804 $tid_field = $query->addField('test_task', 'tid'); |
|
2805 $pid_field = $query->addField('test_task', 'pid'); |
|
2806 $task_field = $query->addField('test_task', 'task'); |
|
2807 $people_alias = $query->join('test', 'people', "test_task.pid = people.id"); |
|
2808 $name_field = $query->addField($people_alias, 'name', 'name'); |
|
2809 $query->condition('test_task.tid', '1'); |
|
2810 $query->orderBy($tid_field); |
|
2811 $query->addTag('database_test_alter_change_conditional'); |
|
2812 |
|
2813 $result = $query->execute(); |
|
2814 |
|
2815 $records = $result->fetchAll(); |
|
2816 |
|
2817 $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); |
|
2818 $this->assertEqual($records[0]->$name_field, 'John', 'Correct data retrieved.'); |
|
2819 $this->assertEqual($records[0]->$tid_field, 2, 'Correct data retrieved.'); |
|
2820 $this->assertEqual($records[0]->$pid_field, 1, 'Correct data retrieved.'); |
|
2821 $this->assertEqual($records[0]->$task_field, 'sleep', 'Correct data retrieved.'); |
|
2822 } |
|
2823 |
|
2824 /** |
|
2825 * Test that we can alter the fields of a query. |
|
2826 */ |
|
2827 function testAlterChangeFields() { |
|
2828 $query = db_select('test'); |
|
2829 $name_field = $query->addField('test', 'name'); |
|
2830 $age_field = $query->addField('test', 'age', 'age'); |
|
2831 $query->orderBy('name'); |
|
2832 $query->addTag('database_test_alter_change_fields'); |
|
2833 |
|
2834 $record = $query->execute()->fetch(); |
|
2835 $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.'); |
|
2836 $this->assertFalse(isset($record->$age_field), 'Age field not found, as intended.'); |
|
2837 } |
|
2838 |
|
2839 /** |
|
2840 * Test that we can alter expressions in the query. |
|
2841 */ |
|
2842 function testAlterExpression() { |
|
2843 $query = db_select('test'); |
|
2844 $name_field = $query->addField('test', 'name'); |
|
2845 $age_field = $query->addExpression("age*2", 'double_age'); |
|
2846 $query->condition('age', 27); |
|
2847 $query->addTag('database_test_alter_change_expressions'); |
|
2848 $result = $query->execute(); |
|
2849 |
|
2850 // Ensure that we got the right record. |
|
2851 $record = $result->fetch(); |
|
2852 |
|
2853 $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); |
|
2854 $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.'); |
|
2855 } |
|
2856 |
|
2857 /** |
|
2858 * Test that we can remove a range() value from a query. This also tests hook_query_TAG_alter(). |
|
2859 */ |
|
2860 function testAlterRemoveRange() { |
|
2861 $query = db_select('test'); |
|
2862 $query->addField('test', 'name'); |
|
2863 $query->addField('test', 'age', 'age'); |
|
2864 $query->range(0, 2); |
|
2865 $query->addTag('database_test_alter_remove_range'); |
|
2866 |
|
2867 $num_records = count($query->execute()->fetchAll()); |
|
2868 |
|
2869 $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); |
|
2870 } |
|
2871 |
|
2872 /** |
|
2873 * Test that we can do basic alters on subqueries. |
|
2874 */ |
|
2875 function testSimpleAlterSubquery() { |
|
2876 // Create a sub-query with an alter tag. |
|
2877 $subquery = db_select('test', 'p'); |
|
2878 $subquery->addField('p', 'name'); |
|
2879 $subquery->addField('p', 'id'); |
|
2880 // Pick out George. |
|
2881 $subquery->condition('age', 27); |
|
2882 $subquery->addExpression("age*2", 'double_age'); |
|
2883 // This query alter should change it to age * 3. |
|
2884 $subquery->addTag('database_test_alter_change_expressions'); |
|
2885 |
|
2886 // Create a main query and join to sub-query. |
|
2887 $query = db_select('test_task', 'tt'); |
|
2888 $query->join($subquery, 'pq', 'pq.id = tt.pid'); |
|
2889 $age_field = $query->addField('pq', 'double_age'); |
|
2890 $name_field = $query->addField('pq', 'name'); |
|
2891 |
|
2892 $record = $query->execute()->fetch(); |
|
2893 $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); |
|
2894 $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.'); |
|
2895 } |
|
2896 } |
|
2897 |
|
2898 /** |
|
2899 * Regression tests. |
|
2900 */ |
|
2901 class DatabaseRegressionTestCase extends DatabaseTestCase { |
|
2902 |
|
2903 public static function getInfo() { |
|
2904 return array( |
|
2905 'name' => 'Regression tests', |
|
2906 'description' => 'Regression tests cases for the database layer.', |
|
2907 'group' => 'Database', |
|
2908 ); |
|
2909 } |
|
2910 |
|
2911 /** |
|
2912 * Regression test for #310447. |
|
2913 * |
|
2914 * Tries to insert non-ascii UTF-8 data in a database column and checks |
|
2915 * if its stored properly. |
|
2916 */ |
|
2917 function testRegression_310447() { |
|
2918 // That's a 255 character UTF-8 string. |
|
2919 $name = str_repeat("é", 255); |
|
2920 db_insert('test') |
|
2921 ->fields(array( |
|
2922 'name' => $name, |
|
2923 'age' => 20, |
|
2924 'job' => 'Dancer', |
|
2925 ))->execute(); |
|
2926 |
|
2927 $from_database = db_query('SELECT name FROM {test} WHERE name = :name', array(':name' => $name))->fetchField(); |
|
2928 $this->assertIdentical($name, $from_database, "The database handles UTF-8 characters cleanly."); |
|
2929 } |
|
2930 |
|
2931 /** |
|
2932 * Test the db_table_exists() function. |
|
2933 */ |
|
2934 function testDBTableExists() { |
|
2935 $this->assertIdentical(TRUE, db_table_exists('node'), 'Returns true for existent table.'); |
|
2936 $this->assertIdentical(FALSE, db_table_exists('nosuchtable'), 'Returns false for nonexistent table.'); |
|
2937 } |
|
2938 |
|
2939 /** |
|
2940 * Test the db_field_exists() function. |
|
2941 */ |
|
2942 function testDBFieldExists() { |
|
2943 $this->assertIdentical(TRUE, db_field_exists('node', 'nid'), 'Returns true for existent column.'); |
|
2944 $this->assertIdentical(FALSE, db_field_exists('node', 'nosuchcolumn'), 'Returns false for nonexistent column.'); |
|
2945 } |
|
2946 |
|
2947 /** |
|
2948 * Test the db_index_exists() function. |
|
2949 */ |
|
2950 function testDBIndexExists() { |
|
2951 $this->assertIdentical(TRUE, db_index_exists('node', 'node_created'), 'Returns true for existent index.'); |
|
2952 $this->assertIdentical(FALSE, db_index_exists('node', 'nosuchindex'), 'Returns false for nonexistent index.'); |
|
2953 } |
|
2954 } |
|
2955 |
|
2956 /** |
|
2957 * Query logging tests. |
|
2958 */ |
|
2959 class DatabaseLoggingTestCase extends DatabaseTestCase { |
|
2960 |
|
2961 public static function getInfo() { |
|
2962 return array( |
|
2963 'name' => 'Query logging', |
|
2964 'description' => 'Test the query logging facility.', |
|
2965 'group' => 'Database', |
|
2966 ); |
|
2967 } |
|
2968 |
|
2969 /** |
|
2970 * Test that we can log the existence of a query. |
|
2971 */ |
|
2972 function testEnableLogging() { |
|
2973 $log = Database::startLog('testing'); |
|
2974 |
|
2975 db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol(); |
|
2976 db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchCol(); |
|
2977 |
|
2978 // Trigger a call that does not have file in the backtrace. |
|
2979 call_user_func_array('db_query', array('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo')))->fetchCol(); |
|
2980 |
|
2981 $queries = Database::getLog('testing', 'default'); |
|
2982 |
|
2983 $this->assertEqual(count($queries), 3, 'Correct number of queries recorded.'); |
|
2984 |
|
2985 foreach ($queries as $query) { |
|
2986 $this->assertEqual($query['caller']['function'], __FUNCTION__, 'Correct function in query log.'); |
|
2987 } |
|
2988 } |
|
2989 |
|
2990 /** |
|
2991 * Test that we can run two logs in parallel. |
|
2992 */ |
|
2993 function testEnableMultiLogging() { |
|
2994 Database::startLog('testing1'); |
|
2995 |
|
2996 db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol(); |
|
2997 |
|
2998 Database::startLog('testing2'); |
|
2999 |
|
3000 db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchCol(); |
|
3001 |
|
3002 $queries1 = Database::getLog('testing1'); |
|
3003 $queries2 = Database::getLog('testing2'); |
|
3004 |
|
3005 $this->assertEqual(count($queries1), 2, 'Correct number of queries recorded for log 1.'); |
|
3006 $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for log 2.'); |
|
3007 } |
|
3008 |
|
3009 /** |
|
3010 * Test that we can log queries against multiple targets on the same connection. |
|
3011 */ |
|
3012 function testEnableTargetLogging() { |
|
3013 // Clone the master credentials to a slave connection and to another fake |
|
3014 // connection. |
|
3015 $connection_info = Database::getConnectionInfo('default'); |
|
3016 Database::addConnectionInfo('default', 'slave', $connection_info['default']); |
|
3017 |
|
3018 Database::startLog('testing1'); |
|
3019 |
|
3020 db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol(); |
|
3021 |
|
3022 db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'slave'));//->fetchCol(); |
|
3023 |
|
3024 $queries1 = Database::getLog('testing1'); |
|
3025 |
|
3026 $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.'); |
|
3027 $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.'); |
|
3028 $this->assertEqual($queries1[1]['target'], 'slave', 'Second query used slave target.'); |
|
3029 } |
|
3030 |
|
3031 /** |
|
3032 * Test that logs to separate targets collapse to the same connection properly. |
|
3033 * |
|
3034 * This test is identical to the one above, except that it doesn't create |
|
3035 * a fake target so the query should fall back to running on the default |
|
3036 * target. |
|
3037 */ |
|
3038 function testEnableTargetLoggingNoTarget() { |
|
3039 Database::startLog('testing1'); |
|
3040 |
|
3041 db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol(); |
|
3042 |
|
3043 // We use "fake" here as a target because any non-existent target will do. |
|
3044 // However, because all of the tests in this class share a single page |
|
3045 // request there is likely to be a target of "slave" from one of the other |
|
3046 // unit tests, so we use a target here that we know with absolute certainty |
|
3047 // does not exist. |
|
3048 db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'fake'))->fetchCol(); |
|
3049 |
|
3050 $queries1 = Database::getLog('testing1'); |
|
3051 |
|
3052 $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.'); |
|
3053 $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.'); |
|
3054 $this->assertEqual($queries1[1]['target'], 'default', 'Second query used default target as fallback.'); |
|
3055 } |
|
3056 |
|
3057 /** |
|
3058 * Test that we can log queries separately on different connections. |
|
3059 */ |
|
3060 function testEnableMultiConnectionLogging() { |
|
3061 // Clone the master credentials to a fake connection. |
|
3062 // That both connections point to the same physical database is irrelevant. |
|
3063 $connection_info = Database::getConnectionInfo('default'); |
|
3064 Database::addConnectionInfo('test2', 'default', $connection_info['default']); |
|
3065 |
|
3066 Database::startLog('testing1'); |
|
3067 Database::startLog('testing1', 'test2'); |
|
3068 |
|
3069 db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol(); |
|
3070 |
|
3071 $old_key = db_set_active('test2'); |
|
3072 |
|
3073 db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'slave'))->fetchCol(); |
|
3074 |
|
3075 db_set_active($old_key); |
|
3076 |
|
3077 $queries1 = Database::getLog('testing1'); |
|
3078 $queries2 = Database::getLog('testing1', 'test2'); |
|
3079 |
|
3080 $this->assertEqual(count($queries1), 1, 'Correct number of queries recorded for first connection.'); |
|
3081 $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.'); |
|
3082 } |
|
3083 } |
|
3084 |
|
3085 /** |
|
3086 * Query serialization tests. |
|
3087 */ |
|
3088 class DatabaseSerializeQueryTestCase extends DatabaseTestCase { |
|
3089 public static function getInfo() { |
|
3090 return array( |
|
3091 'name' => 'Serialize query', |
|
3092 'description' => 'Test serializing and unserializing a query.', |
|
3093 'group' => 'Database', |
|
3094 ); |
|
3095 } |
|
3096 |
|
3097 /** |
|
3098 * Confirm that a query can be serialized and unserialized. |
|
3099 */ |
|
3100 function testSerializeQuery() { |
|
3101 $query = db_select('test'); |
|
3102 $query->addField('test', 'age'); |
|
3103 $query->condition('name', 'Ringo'); |
|
3104 // If this doesn't work, it will throw an exception, so no need for an |
|
3105 // assertion. |
|
3106 $query = unserialize(serialize($query)); |
|
3107 $results = $query->execute()->fetchCol(); |
|
3108 $this->assertEqual($results[0], 28, 'Query properly executed after unserialization.'); |
|
3109 } |
|
3110 } |
|
3111 |
|
3112 /** |
|
3113 * Range query tests. |
|
3114 */ |
|
3115 class DatabaseRangeQueryTestCase extends DrupalWebTestCase { |
|
3116 public static function getInfo() { |
|
3117 return array( |
|
3118 'name' => 'Range query test', |
|
3119 'description' => 'Test the Range query functionality.', |
|
3120 'group' => 'Database', |
|
3121 ); |
|
3122 } |
|
3123 |
|
3124 function setUp() { |
|
3125 parent::setUp('database_test'); |
|
3126 } |
|
3127 |
|
3128 /** |
|
3129 * Confirm that range query work and return correct result. |
|
3130 */ |
|
3131 function testRangeQuery() { |
|
3132 // Test if return correct number of rows. |
|
3133 $range_rows = db_query_range("SELECT name FROM {system} ORDER BY name", 2, 3)->fetchAll(); |
|
3134 $this->assertEqual(count($range_rows), 3, 'Range query work and return correct number of rows.'); |
|
3135 |
|
3136 // Test if return target data. |
|
3137 $raw_rows = db_query('SELECT name FROM {system} ORDER BY name')->fetchAll(); |
|
3138 $raw_rows = array_slice($raw_rows, 2, 3); |
|
3139 $this->assertEqual($range_rows, $raw_rows, 'Range query work and return target data.'); |
|
3140 } |
|
3141 } |
|
3142 |
|
3143 /** |
|
3144 * Temporary query tests. |
|
3145 */ |
|
3146 class DatabaseTemporaryQueryTestCase extends DrupalWebTestCase { |
|
3147 public static function getInfo() { |
|
3148 return array( |
|
3149 'name' => 'Temporary query test', |
|
3150 'description' => 'Test the temporary query functionality.', |
|
3151 'group' => 'Database', |
|
3152 ); |
|
3153 } |
|
3154 |
|
3155 function setUp() { |
|
3156 parent::setUp('database_test'); |
|
3157 } |
|
3158 |
|
3159 /** |
|
3160 * Return the number of rows of a table. |
|
3161 */ |
|
3162 function countTableRows($table_name) { |
|
3163 return db_select($table_name)->countQuery()->execute()->fetchField(); |
|
3164 } |
|
3165 |
|
3166 /** |
|
3167 * Confirm that temporary tables work and are limited to one request. |
|
3168 */ |
|
3169 function testTemporaryQuery() { |
|
3170 $this->drupalGet('database_test/db_query_temporary'); |
|
3171 $data = json_decode($this->drupalGetContent()); |
|
3172 if ($data) { |
|
3173 $this->assertEqual($this->countTableRows("system"), $data->row_count, 'The temporary table contains the correct amount of rows.'); |
|
3174 $this->assertFalse(db_table_exists($data->table_name), 'The temporary table is, indeed, temporary.'); |
|
3175 } |
|
3176 else { |
|
3177 $this->fail("The creation of the temporary table failed."); |
|
3178 } |
|
3179 |
|
3180 // Now try to run two db_query_temporary() in the same request. |
|
3181 $table_name_system = db_query_temporary('SELECT status FROM {system}', array()); |
|
3182 $table_name_users = db_query_temporary('SELECT uid FROM {users}', array()); |
|
3183 |
|
3184 $this->assertEqual($this->countTableRows($table_name_system), $this->countTableRows("system"), 'A temporary table was created successfully in this request.'); |
|
3185 $this->assertEqual($this->countTableRows($table_name_users), $this->countTableRows("users"), 'A second temporary table was created successfully in this request.'); |
|
3186 |
|
3187 // Check that leading whitespace and comments do not cause problems |
|
3188 // in the modified query. |
|
3189 $sql = " |
|
3190 -- Let's select some rows into a temporary table |
|
3191 SELECT name FROM {test} |
|
3192 "; |
|
3193 $table_name_test = db_query_temporary($sql, array()); |
|
3194 $this->assertEqual($this->countTableRows($table_name_test), $this->countTableRows('test'), 'Leading white space and comments do not interfere with temporary table creation.'); |
|
3195 } |
|
3196 } |
|
3197 |
|
3198 /** |
|
3199 * Test how the current database driver interprets the SQL syntax. |
|
3200 * |
|
3201 * In order to ensure consistent SQL handling throughout Drupal |
|
3202 * across multiple kinds of database systems, we test that the |
|
3203 * database system interprets SQL syntax in an expected fashion. |
|
3204 */ |
|
3205 class DatabaseBasicSyntaxTestCase extends DatabaseTestCase { |
|
3206 public static function getInfo() { |
|
3207 return array( |
|
3208 'name' => 'Basic SQL syntax tests', |
|
3209 'description' => 'Test SQL syntax interpretation.', |
|
3210 'group' => 'Database', |
|
3211 ); |
|
3212 } |
|
3213 |
|
3214 function setUp() { |
|
3215 parent::setUp('database_test'); |
|
3216 } |
|
3217 |
|
3218 /** |
|
3219 * Test for string concatenation. |
|
3220 */ |
|
3221 function testBasicConcat() { |
|
3222 $result = db_query('SELECT CONCAT(:a1, CONCAT(:a2, CONCAT(:a3, CONCAT(:a4, :a5))))', array( |
|
3223 ':a1' => 'This', |
|
3224 ':a2' => ' ', |
|
3225 ':a3' => 'is', |
|
3226 ':a4' => ' a ', |
|
3227 ':a5' => 'test.', |
|
3228 )); |
|
3229 $this->assertIdentical($result->fetchField(), 'This is a test.', 'Basic CONCAT works.'); |
|
3230 } |
|
3231 |
|
3232 /** |
|
3233 * Test for string concatenation with field values. |
|
3234 */ |
|
3235 function testFieldConcat() { |
|
3236 $result = db_query('SELECT CONCAT(:a1, CONCAT(name, CONCAT(:a2, CONCAT(age, :a3)))) FROM {test} WHERE age = :age', array( |
|
3237 ':a1' => 'The age of ', |
|
3238 ':a2' => ' is ', |
|
3239 ':a3' => '.', |
|
3240 ':age' => 25, |
|
3241 )); |
|
3242 $this->assertIdentical($result->fetchField(), 'The age of John is 25.', 'Field CONCAT works.'); |
|
3243 } |
|
3244 |
|
3245 /** |
|
3246 * Test escaping of LIKE wildcards. |
|
3247 */ |
|
3248 function testLikeEscape() { |
|
3249 db_insert('test') |
|
3250 ->fields(array( |
|
3251 'name' => 'Ring_', |
|
3252 )) |
|
3253 ->execute(); |
|
3254 |
|
3255 // Match both "Ringo" and "Ring_". |
|
3256 $num_matches = db_select('test', 't') |
|
3257 ->condition('name', 'Ring_', 'LIKE') |
|
3258 ->countQuery() |
|
3259 ->execute() |
|
3260 ->fetchField(); |
|
3261 $this->assertIdentical($num_matches, '2', 'Found 2 records.'); |
|
3262 // Match only "Ring_" using a LIKE expression with no wildcards. |
|
3263 $num_matches = db_select('test', 't') |
|
3264 ->condition('name', db_like('Ring_'), 'LIKE') |
|
3265 ->countQuery() |
|
3266 ->execute() |
|
3267 ->fetchField(); |
|
3268 $this->assertIdentical($num_matches, '1', 'Found 1 record.'); |
|
3269 } |
|
3270 |
|
3271 /** |
|
3272 * Test LIKE query containing a backslash. |
|
3273 */ |
|
3274 function testLikeBackslash() { |
|
3275 db_insert('test') |
|
3276 ->fields(array('name')) |
|
3277 ->values(array( |
|
3278 'name' => 'abcde\f', |
|
3279 )) |
|
3280 ->values(array( |
|
3281 'name' => 'abc%\_', |
|
3282 )) |
|
3283 ->execute(); |
|
3284 |
|
3285 // Match both rows using a LIKE expression with two wildcards and a verbatim |
|
3286 // backslash. |
|
3287 $num_matches = db_select('test', 't') |
|
3288 ->condition('name', 'abc%\\\\_', 'LIKE') |
|
3289 ->countQuery() |
|
3290 ->execute() |
|
3291 ->fetchField(); |
|
3292 $this->assertIdentical($num_matches, '2', 'Found 2 records.'); |
|
3293 // Match only the former using a LIKE expression with no wildcards. |
|
3294 $num_matches = db_select('test', 't') |
|
3295 ->condition('name', db_like('abc%\_'), 'LIKE') |
|
3296 ->countQuery() |
|
3297 ->execute() |
|
3298 ->fetchField(); |
|
3299 $this->assertIdentical($num_matches, '1', 'Found 1 record.'); |
|
3300 } |
|
3301 } |
|
3302 |
|
3303 /** |
|
3304 * Test case sensitivity handling. |
|
3305 */ |
|
3306 class DatabaseCaseSensitivityTestCase extends DatabaseTestCase { |
|
3307 public static function getInfo() { |
|
3308 return array( |
|
3309 'name' => 'Case sensitivity', |
|
3310 'description' => 'Test handling case sensitive collation.', |
|
3311 'group' => 'Database', |
|
3312 ); |
|
3313 } |
|
3314 |
|
3315 /** |
|
3316 * Test BINARY collation in MySQL. |
|
3317 */ |
|
3318 function testCaseSensitiveInsert() { |
|
3319 $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
3320 |
|
3321 $john = db_insert('test') |
|
3322 ->fields(array( |
|
3323 'name' => 'john', // <- A record already exists with name 'John'. |
|
3324 'age' => 2, |
|
3325 'job' => 'Baby', |
|
3326 )) |
|
3327 ->execute(); |
|
3328 |
|
3329 $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); |
|
3330 $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.'); |
|
3331 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField(); |
|
3332 $this->assertIdentical($saved_age, '2', 'Can retrieve after inserting.'); |
|
3333 } |
|
3334 } |
|
3335 |
|
3336 /** |
|
3337 * Test invalid data handling. |
|
3338 */ |
|
3339 class DatabaseInvalidDataTestCase extends DatabaseTestCase { |
|
3340 public static function getInfo() { |
|
3341 return array( |
|
3342 'name' => 'Invalid data', |
|
3343 'description' => 'Test handling of some invalid data.', |
|
3344 'group' => 'Database', |
|
3345 ); |
|
3346 } |
|
3347 |
|
3348 function setUp() { |
|
3349 parent::setUp('database_test'); |
|
3350 } |
|
3351 |
|
3352 /** |
|
3353 * Traditional SQL database systems abort inserts when invalid data is encountered. |
|
3354 */ |
|
3355 function testInsertDuplicateData() { |
|
3356 // Try to insert multiple records where at least one has bad data. |
|
3357 try { |
|
3358 db_insert('test') |
|
3359 ->fields(array('name', 'age', 'job')) |
|
3360 ->values(array( |
|
3361 'name' => 'Elvis', |
|
3362 'age' => 63, |
|
3363 'job' => 'Singer', |
|
3364 ))->values(array( |
|
3365 'name' => 'John', // <-- Duplicate value on unique field. |
|
3366 'age' => 17, |
|
3367 'job' => 'Consultant', |
|
3368 )) |
|
3369 ->values(array( |
|
3370 'name' => 'Frank', |
|
3371 'age' => 75, |
|
3372 'job' => 'Singer', |
|
3373 )) |
|
3374 ->execute(); |
|
3375 $this->fail('Insert succeedded when it should not have.'); |
|
3376 } |
|
3377 catch (Exception $e) { |
|
3378 // Check if the first record was inserted. |
|
3379 $name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 63))->fetchField(); |
|
3380 |
|
3381 if ($name == 'Elvis') { |
|
3382 if (!Database::getConnection()->supportsTransactions()) { |
|
3383 // This is an expected fail. |
|
3384 // Database engines that don't support transactions can leave partial |
|
3385 // inserts in place when an error occurs. This is the case for MySQL |
|
3386 // when running on a MyISAM table. |
|
3387 $this->pass("The whole transaction has not been rolled-back when a duplicate key insert occurs, this is expected because the database doesn't support transactions"); |
|
3388 } |
|
3389 else { |
|
3390 $this->fail('The whole transaction is rolled back when a duplicate key insert occurs.'); |
|
3391 } |
|
3392 } |
|
3393 else { |
|
3394 $this->pass('The whole transaction is rolled back when a duplicate key insert occurs.'); |
|
3395 } |
|
3396 |
|
3397 // Ensure the other values were not inserted. |
|
3398 $record = db_select('test') |
|
3399 ->fields('test', array('name', 'age')) |
|
3400 ->condition('age', array(17, 75), 'IN') |
|
3401 ->execute()->fetchObject(); |
|
3402 |
|
3403 $this->assertFalse($record, 'The rest of the insert aborted as expected.'); |
|
3404 } |
|
3405 } |
|
3406 |
|
3407 } |
|
3408 |
|
3409 /** |
|
3410 * Drupal-specific SQL syntax tests. |
|
3411 */ |
|
3412 class DatabaseQueryTestCase extends DatabaseTestCase { |
|
3413 public static function getInfo() { |
|
3414 return array( |
|
3415 'name' => 'Custom query syntax tests', |
|
3416 'description' => 'Test Drupal\'s extended prepared statement syntax..', |
|
3417 'group' => 'Database', |
|
3418 ); |
|
3419 } |
|
3420 |
|
3421 function setUp() { |
|
3422 parent::setUp('database_test'); |
|
3423 } |
|
3424 |
|
3425 /** |
|
3426 * Test that we can specify an array of values in the query by simply passing in an array. |
|
3427 */ |
|
3428 function testArraySubstitution() { |
|
3429 $names = db_query('SELECT name FROM {test} WHERE age IN (:ages) ORDER BY age', array(':ages' => array(25, 26, 27)))->fetchAll(); |
|
3430 |
|
3431 $this->assertEqual(count($names), 3, 'Correct number of names returned'); |
|
3432 } |
|
3433 |
|
3434 /** |
|
3435 * Test SQL injection via database query array arguments. |
|
3436 */ |
|
3437 public function testArrayArgumentsSQLInjection() { |
|
3438 // Attempt SQL injection and verify that it does not work. |
|
3439 $condition = array( |
|
3440 "1 ;INSERT INTO {test} (name) VALUES ('test12345678'); -- " => '', |
|
3441 '1' => '', |
|
3442 ); |
|
3443 try { |
|
3444 db_query("SELECT * FROM {test} WHERE name = :name", array(':name' => $condition))->fetchObject(); |
|
3445 $this->fail('SQL injection attempt via array arguments should result in a PDOException.'); |
|
3446 } |
|
3447 catch (PDOException $e) { |
|
3448 $this->pass('SQL injection attempt via array arguments should result in a PDOException.'); |
|
3449 } |
|
3450 |
|
3451 // Test that the insert query that was used in the SQL injection attempt did |
|
3452 // not result in a row being inserted in the database. |
|
3453 $result = db_select('test') |
|
3454 ->condition('name', 'test12345678') |
|
3455 ->countQuery() |
|
3456 ->execute() |
|
3457 ->fetchField(); |
|
3458 $this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.'); |
|
3459 } |
|
3460 |
|
3461 } |
|
3462 |
|
3463 /** |
|
3464 * Test transaction support, particularly nesting. |
|
3465 * |
|
3466 * We test nesting by having two transaction layers, an outer and inner. The |
|
3467 * outer layer encapsulates the inner layer. Our transaction nesting abstraction |
|
3468 * should allow the outer layer function to call any function it wants, |
|
3469 * especially the inner layer that starts its own transaction, and be |
|
3470 * confident that, when the function it calls returns, its own transaction |
|
3471 * is still "alive." |
|
3472 * |
|
3473 * Call structure: |
|
3474 * transactionOuterLayer() |
|
3475 * Start transaction |
|
3476 * transactionInnerLayer() |
|
3477 * Start transaction (does nothing in database) |
|
3478 * [Maybe decide to roll back] |
|
3479 * Do more stuff |
|
3480 * Should still be in transaction A |
|
3481 * |
|
3482 */ |
|
3483 class DatabaseTransactionTestCase extends DatabaseTestCase { |
|
3484 |
|
3485 public static function getInfo() { |
|
3486 return array( |
|
3487 'name' => 'Transaction tests', |
|
3488 'description' => 'Test the transaction abstraction system.', |
|
3489 'group' => 'Database', |
|
3490 ); |
|
3491 } |
|
3492 |
|
3493 /** |
|
3494 * Helper method for transaction unit test. |
|
3495 * |
|
3496 * This "outer layer" transaction starts and then encapsulates the |
|
3497 * "inner layer" transaction. This nesting is used to evaluate whether the |
|
3498 * database transaction API properly supports nesting. By "properly supports," |
|
3499 * we mean the outer transaction continues to exist regardless of what |
|
3500 * functions are called and whether those functions start their own |
|
3501 * transactions. |
|
3502 * |
|
3503 * In contrast, a typical database would commit the outer transaction, start |
|
3504 * a new transaction for the inner layer, commit the inner layer transaction, |
|
3505 * and then be confused when the outer layer transaction tries to commit its |
|
3506 * transaction (which was already committed when the inner transaction |
|
3507 * started). |
|
3508 * |
|
3509 * @param $suffix |
|
3510 * Suffix to add to field values to differentiate tests. |
|
3511 * @param $rollback |
|
3512 * Whether or not to try rolling back the transaction when we're done. |
|
3513 * @param $ddl_statement |
|
3514 * Whether to execute a DDL statement during the inner transaction. |
|
3515 */ |
|
3516 protected function transactionOuterLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) { |
|
3517 $connection = Database::getConnection(); |
|
3518 $depth = $connection->transactionDepth(); |
|
3519 $txn = db_transaction(); |
|
3520 |
|
3521 // Insert a single row into the testing table. |
|
3522 db_insert('test') |
|
3523 ->fields(array( |
|
3524 'name' => 'David' . $suffix, |
|
3525 'age' => '24', |
|
3526 )) |
|
3527 ->execute(); |
|
3528 |
|
3529 $this->assertTrue($connection->inTransaction(), 'In transaction before calling nested transaction.'); |
|
3530 |
|
3531 // We're already in a transaction, but we call ->transactionInnerLayer |
|
3532 // to nest another transaction inside the current one. |
|
3533 $this->transactionInnerLayer($suffix, $rollback, $ddl_statement); |
|
3534 |
|
3535 $this->assertTrue($connection->inTransaction(), 'In transaction after calling nested transaction.'); |
|
3536 |
|
3537 if ($rollback) { |
|
3538 // Roll back the transaction, if requested. |
|
3539 // This rollback should propagate to the last savepoint. |
|
3540 $txn->rollback(); |
|
3541 $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().'); |
|
3542 } |
|
3543 } |
|
3544 |
|
3545 /** |
|
3546 * Helper method for transaction unit tests. This "inner layer" transaction |
|
3547 * is either used alone or nested inside of the "outer layer" transaction. |
|
3548 * |
|
3549 * @param $suffix |
|
3550 * Suffix to add to field values to differentiate tests. |
|
3551 * @param $rollback |
|
3552 * Whether or not to try rolling back the transaction when we're done. |
|
3553 * @param $ddl_statement |
|
3554 * Whether to execute a DDL statement during the transaction. |
|
3555 */ |
|
3556 protected function transactionInnerLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) { |
|
3557 $connection = Database::getConnection(); |
|
3558 |
|
3559 $depth = $connection->transactionDepth(); |
|
3560 // Start a transaction. If we're being called from ->transactionOuterLayer, |
|
3561 // then we're already in a transaction. Normally, that would make starting |
|
3562 // a transaction here dangerous, but the database API handles this problem |
|
3563 // for us by tracking the nesting and avoiding the danger. |
|
3564 $txn = db_transaction(); |
|
3565 |
|
3566 $depth2 = $connection->transactionDepth(); |
|
3567 $this->assertTrue($depth < $depth2, 'Transaction depth is has increased with new transaction.'); |
|
3568 |
|
3569 // Insert a single row into the testing table. |
|
3570 db_insert('test') |
|
3571 ->fields(array( |
|
3572 'name' => 'Daniel' . $suffix, |
|
3573 'age' => '19', |
|
3574 )) |
|
3575 ->execute(); |
|
3576 |
|
3577 $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.'); |
|
3578 |
|
3579 if ($ddl_statement) { |
|
3580 $table = array( |
|
3581 'fields' => array( |
|
3582 'id' => array( |
|
3583 'type' => 'serial', |
|
3584 'unsigned' => TRUE, |
|
3585 'not null' => TRUE, |
|
3586 ), |
|
3587 ), |
|
3588 'primary key' => array('id'), |
|
3589 ); |
|
3590 db_create_table('database_test_1', $table); |
|
3591 |
|
3592 $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.'); |
|
3593 } |
|
3594 |
|
3595 if ($rollback) { |
|
3596 // Roll back the transaction, if requested. |
|
3597 // This rollback should propagate to the last savepoint. |
|
3598 $txn->rollback(); |
|
3599 $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().'); |
|
3600 } |
|
3601 } |
|
3602 |
|
3603 /** |
|
3604 * Test transaction rollback on a database that supports transactions. |
|
3605 * |
|
3606 * If the active connection does not support transactions, this test does nothing. |
|
3607 */ |
|
3608 function testTransactionRollBackSupported() { |
|
3609 // This test won't work right if transactions are not supported. |
|
3610 if (!Database::getConnection()->supportsTransactions()) { |
|
3611 return; |
|
3612 } |
|
3613 try { |
|
3614 // Create two nested transactions. Roll back from the inner one. |
|
3615 $this->transactionOuterLayer('B', TRUE); |
|
3616 |
|
3617 // Neither of the rows we inserted in the two transaction layers |
|
3618 // should be present in the tables post-rollback. |
|
3619 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField(); |
|
3620 $this->assertNotIdentical($saved_age, '24', 'Cannot retrieve DavidB row after commit.'); |
|
3621 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField(); |
|
3622 $this->assertNotIdentical($saved_age, '19', 'Cannot retrieve DanielB row after commit.'); |
|
3623 } |
|
3624 catch (Exception $e) { |
|
3625 $this->fail($e->getMessage()); |
|
3626 } |
|
3627 } |
|
3628 |
|
3629 /** |
|
3630 * Test transaction rollback on a database that does not support transactions. |
|
3631 * |
|
3632 * If the active driver supports transactions, this test does nothing. |
|
3633 */ |
|
3634 function testTransactionRollBackNotSupported() { |
|
3635 // This test won't work right if transactions are supported. |
|
3636 if (Database::getConnection()->supportsTransactions()) { |
|
3637 return; |
|
3638 } |
|
3639 try { |
|
3640 // Create two nested transactions. Attempt to roll back from the inner one. |
|
3641 $this->transactionOuterLayer('B', TRUE); |
|
3642 |
|
3643 // Because our current database claims to not support transactions, |
|
3644 // the inserted rows should be present despite the attempt to roll back. |
|
3645 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField(); |
|
3646 $this->assertIdentical($saved_age, '24', 'DavidB not rolled back, since transactions are not supported.'); |
|
3647 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField(); |
|
3648 $this->assertIdentical($saved_age, '19', 'DanielB not rolled back, since transactions are not supported.'); |
|
3649 } |
|
3650 catch (Exception $e) { |
|
3651 $this->fail($e->getMessage()); |
|
3652 } |
|
3653 } |
|
3654 |
|
3655 /** |
|
3656 * Test committed transaction. |
|
3657 * |
|
3658 * The behavior of this test should be identical for connections that support |
|
3659 * transactions and those that do not. |
|
3660 */ |
|
3661 function testCommittedTransaction() { |
|
3662 try { |
|
3663 // Create two nested transactions. The changes should be committed. |
|
3664 $this->transactionOuterLayer('A'); |
|
3665 |
|
3666 // Because we committed, both of the inserted rows should be present. |
|
3667 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidA'))->fetchField(); |
|
3668 $this->assertIdentical($saved_age, '24', 'Can retrieve DavidA row after commit.'); |
|
3669 $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielA'))->fetchField(); |
|
3670 $this->assertIdentical($saved_age, '19', 'Can retrieve DanielA row after commit.'); |
|
3671 } |
|
3672 catch (Exception $e) { |
|
3673 $this->fail($e->getMessage()); |
|
3674 } |
|
3675 } |
|
3676 |
|
3677 /** |
|
3678 * Test the compatibility of transactions with DDL statements. |
|
3679 */ |
|
3680 function testTransactionWithDdlStatement() { |
|
3681 // First, test that a commit works normally, even with DDL statements. |
|
3682 $transaction = db_transaction(); |
|
3683 $this->insertRow('row'); |
|
3684 $this->executeDDLStatement(); |
|
3685 unset($transaction); |
|
3686 $this->assertRowPresent('row'); |
|
3687 |
|
3688 // Even in different order. |
|
3689 $this->cleanUp(); |
|
3690 $transaction = db_transaction(); |
|
3691 $this->executeDDLStatement(); |
|
3692 $this->insertRow('row'); |
|
3693 unset($transaction); |
|
3694 $this->assertRowPresent('row'); |
|
3695 |
|
3696 // Even with stacking. |
|
3697 $this->cleanUp(); |
|
3698 $transaction = db_transaction(); |
|
3699 $transaction2 = db_transaction(); |
|
3700 $this->executeDDLStatement(); |
|
3701 unset($transaction2); |
|
3702 $transaction3 = db_transaction(); |
|
3703 $this->insertRow('row'); |
|
3704 unset($transaction3); |
|
3705 unset($transaction); |
|
3706 $this->assertRowPresent('row'); |
|
3707 |
|
3708 // A transaction after a DDL statement should still work the same. |
|
3709 $this->cleanUp(); |
|
3710 $transaction = db_transaction(); |
|
3711 $transaction2 = db_transaction(); |
|
3712 $this->executeDDLStatement(); |
|
3713 unset($transaction2); |
|
3714 $transaction3 = db_transaction(); |
|
3715 $this->insertRow('row'); |
|
3716 $transaction3->rollback(); |
|
3717 unset($transaction3); |
|
3718 unset($transaction); |
|
3719 $this->assertRowAbsent('row'); |
|
3720 |
|
3721 // The behavior of a rollback depends on the type of database server. |
|
3722 if (Database::getConnection()->supportsTransactionalDDL()) { |
|
3723 // For database servers that support transactional DDL, a rollback |
|
3724 // of a transaction including DDL statements should be possible. |
|
3725 $this->cleanUp(); |
|
3726 $transaction = db_transaction(); |
|
3727 $this->insertRow('row'); |
|
3728 $this->executeDDLStatement(); |
|
3729 $transaction->rollback(); |
|
3730 unset($transaction); |
|
3731 $this->assertRowAbsent('row'); |
|
3732 |
|
3733 // Including with stacking. |
|
3734 $this->cleanUp(); |
|
3735 $transaction = db_transaction(); |
|
3736 $transaction2 = db_transaction(); |
|
3737 $this->executeDDLStatement(); |
|
3738 unset($transaction2); |
|
3739 $transaction3 = db_transaction(); |
|
3740 $this->insertRow('row'); |
|
3741 unset($transaction3); |
|
3742 $transaction->rollback(); |
|
3743 unset($transaction); |
|
3744 $this->assertRowAbsent('row'); |
|
3745 } |
|
3746 else { |
|
3747 // For database servers that do not support transactional DDL, |
|
3748 // the DDL statement should commit the transaction stack. |
|
3749 $this->cleanUp(); |
|
3750 $transaction = db_transaction(); |
|
3751 $this->insertRow('row'); |
|
3752 $this->executeDDLStatement(); |
|
3753 // Rollback the outer transaction. |
|
3754 try { |
|
3755 $transaction->rollback(); |
|
3756 unset($transaction); |
|
3757 // @TODO: an exception should be triggered here, but is not, because |
|
3758 // "ROLLBACK" fails silently in MySQL if there is no transaction active. |
|
3759 // $this->fail(t('Rolling back a transaction containing DDL should fail.')); |
|
3760 } |
|
3761 catch (DatabaseTransactionNoActiveException $e) { |
|
3762 $this->pass('Rolling back a transaction containing DDL should fail.'); |
|
3763 } |
|
3764 $this->assertRowPresent('row'); |
|
3765 } |
|
3766 } |
|
3767 |
|
3768 /** |
|
3769 * Insert a single row into the testing table. |
|
3770 */ |
|
3771 protected function insertRow($name) { |
|
3772 db_insert('test') |
|
3773 ->fields(array( |
|
3774 'name' => $name, |
|
3775 )) |
|
3776 ->execute(); |
|
3777 } |
|
3778 |
|
3779 /** |
|
3780 * Execute a DDL statement. |
|
3781 */ |
|
3782 protected function executeDDLStatement() { |
|
3783 static $count = 0; |
|
3784 $table = array( |
|
3785 'fields' => array( |
|
3786 'id' => array( |
|
3787 'type' => 'serial', |
|
3788 'unsigned' => TRUE, |
|
3789 'not null' => TRUE, |
|
3790 ), |
|
3791 ), |
|
3792 'primary key' => array('id'), |
|
3793 ); |
|
3794 db_create_table('database_test_' . ++$count, $table); |
|
3795 } |
|
3796 |
|
3797 /** |
|
3798 * Start over for a new test. |
|
3799 */ |
|
3800 protected function cleanUp() { |
|
3801 db_truncate('test') |
|
3802 ->execute(); |
|
3803 } |
|
3804 |
|
3805 /** |
|
3806 * Assert that a given row is present in the test table. |
|
3807 * |
|
3808 * @param $name |
|
3809 * The name of the row. |
|
3810 * @param $message |
|
3811 * The message to log for the assertion. |
|
3812 */ |
|
3813 function assertRowPresent($name, $message = NULL) { |
|
3814 if (!isset($message)) { |
|
3815 $message = format_string('Row %name is present.', array('%name' => $name)); |
|
3816 } |
|
3817 $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField(); |
|
3818 return $this->assertTrue($present, $message); |
|
3819 } |
|
3820 |
|
3821 /** |
|
3822 * Assert that a given row is absent from the test table. |
|
3823 * |
|
3824 * @param $name |
|
3825 * The name of the row. |
|
3826 * @param $message |
|
3827 * The message to log for the assertion. |
|
3828 */ |
|
3829 function assertRowAbsent($name, $message = NULL) { |
|
3830 if (!isset($message)) { |
|
3831 $message = format_string('Row %name is absent.', array('%name' => $name)); |
|
3832 } |
|
3833 $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField(); |
|
3834 return $this->assertFalse($present, $message); |
|
3835 } |
|
3836 |
|
3837 /** |
|
3838 * Test transaction stacking and commit / rollback. |
|
3839 */ |
|
3840 function testTransactionStacking() { |
|
3841 // This test won't work right if transactions are not supported. |
|
3842 if (!Database::getConnection()->supportsTransactions()) { |
|
3843 return; |
|
3844 } |
|
3845 |
|
3846 $database = Database::getConnection(); |
|
3847 |
|
3848 // Standard case: pop the inner transaction before the outer transaction. |
|
3849 $transaction = db_transaction(); |
|
3850 $this->insertRow('outer'); |
|
3851 $transaction2 = db_transaction(); |
|
3852 $this->insertRow('inner'); |
|
3853 // Pop the inner transaction. |
|
3854 unset($transaction2); |
|
3855 $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the inner transaction'); |
|
3856 // Pop the outer transaction. |
|
3857 unset($transaction); |
|
3858 $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the outer transaction'); |
|
3859 $this->assertRowPresent('outer'); |
|
3860 $this->assertRowPresent('inner'); |
|
3861 |
|
3862 // Pop the transaction in a different order they have been pushed. |
|
3863 $this->cleanUp(); |
|
3864 $transaction = db_transaction(); |
|
3865 $this->insertRow('outer'); |
|
3866 $transaction2 = db_transaction(); |
|
3867 $this->insertRow('inner'); |
|
3868 // Pop the outer transaction, nothing should happen. |
|
3869 unset($transaction); |
|
3870 $this->insertRow('inner-after-outer-commit'); |
|
3871 $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction'); |
|
3872 // Pop the inner transaction, the whole transaction should commit. |
|
3873 unset($transaction2); |
|
3874 $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction'); |
|
3875 $this->assertRowPresent('outer'); |
|
3876 $this->assertRowPresent('inner'); |
|
3877 $this->assertRowPresent('inner-after-outer-commit'); |
|
3878 |
|
3879 // Rollback the inner transaction. |
|
3880 $this->cleanUp(); |
|
3881 $transaction = db_transaction(); |
|
3882 $this->insertRow('outer'); |
|
3883 $transaction2 = db_transaction(); |
|
3884 $this->insertRow('inner'); |
|
3885 // Now rollback the inner transaction. |
|
3886 $transaction2->rollback(); |
|
3887 unset($transaction2); |
|
3888 $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction'); |
|
3889 // Pop the outer transaction, it should commit. |
|
3890 $this->insertRow('outer-after-inner-rollback'); |
|
3891 unset($transaction); |
|
3892 $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction'); |
|
3893 $this->assertRowPresent('outer'); |
|
3894 $this->assertRowAbsent('inner'); |
|
3895 $this->assertRowPresent('outer-after-inner-rollback'); |
|
3896 |
|
3897 // Rollback the inner transaction after committing the outer one. |
|
3898 $this->cleanUp(); |
|
3899 $transaction = db_transaction(); |
|
3900 $this->insertRow('outer'); |
|
3901 $transaction2 = db_transaction(); |
|
3902 $this->insertRow('inner'); |
|
3903 // Pop the outer transaction, nothing should happen. |
|
3904 unset($transaction); |
|
3905 $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction'); |
|
3906 // Now rollback the inner transaction, it should rollback. |
|
3907 $transaction2->rollback(); |
|
3908 unset($transaction2); |
|
3909 $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction'); |
|
3910 $this->assertRowPresent('outer'); |
|
3911 $this->assertRowAbsent('inner'); |
|
3912 |
|
3913 // Rollback the outer transaction while the inner transaction is active. |
|
3914 // In that case, an exception will be triggered because we cannot |
|
3915 // ensure that the final result will have any meaning. |
|
3916 $this->cleanUp(); |
|
3917 $transaction = db_transaction(); |
|
3918 $this->insertRow('outer'); |
|
3919 $transaction2 = db_transaction(); |
|
3920 $this->insertRow('inner'); |
|
3921 $transaction3 = db_transaction(); |
|
3922 $this->insertRow('inner2'); |
|
3923 // Rollback the outer transaction. |
|
3924 try { |
|
3925 $transaction->rollback(); |
|
3926 unset($transaction); |
|
3927 $this->fail('Rolling back the outer transaction while the inner transaction is active resulted in an exception.'); |
|
3928 } |
|
3929 catch (DatabaseTransactionOutOfOrderException $e) { |
|
3930 $this->pass('Rolling back the outer transaction while the inner transaction is active resulted in an exception.'); |
|
3931 } |
|
3932 $this->assertFalse($database->inTransaction(), 'No more in a transaction after rolling back the outer transaction'); |
|
3933 // Try to commit one inner transaction. |
|
3934 unset($transaction3); |
|
3935 $this->pass('Trying to commit an inner transaction resulted in an exception.'); |
|
3936 // Try to rollback one inner transaction. |
|
3937 try { |
|
3938 $transaction->rollback(); |
|
3939 unset($transaction2); |
|
3940 $this->fail('Trying to commit an inner transaction resulted in an exception.'); |
|
3941 } |
|
3942 catch (DatabaseTransactionNoActiveException $e) { |
|
3943 $this->pass('Trying to commit an inner transaction resulted in an exception.'); |
|
3944 } |
|
3945 $this->assertRowAbsent('outer'); |
|
3946 $this->assertRowAbsent('inner'); |
|
3947 $this->assertRowAbsent('inner2'); |
|
3948 } |
|
3949 } |
|
3950 |
|
3951 |
|
3952 /** |
|
3953 * Check the sequences API. |
|
3954 */ |
|
3955 class DatabaseNextIdCase extends DrupalWebTestCase { |
|
3956 public static function getInfo() { |
|
3957 return array( |
|
3958 'name' => 'Sequences API', |
|
3959 'description' => 'Test the secondary sequences API.', |
|
3960 'group' => 'Database', |
|
3961 ); |
|
3962 } |
|
3963 |
|
3964 /** |
|
3965 * Test that the sequences API work. |
|
3966 */ |
|
3967 function testDbNextId() { |
|
3968 $first = db_next_id(); |
|
3969 $second = db_next_id(); |
|
3970 // We can test for exact increase in here because we know there is no |
|
3971 // other process operating on these tables -- normally we could only |
|
3972 // expect $second > $first. |
|
3973 $this->assertEqual($first + 1, $second, 'The second call from a sequence provides a number increased by one.'); |
|
3974 $result = db_next_id(1000); |
|
3975 $this->assertEqual($result, 1001, 'Sequence provides a larger number than the existing ID.'); |
|
3976 } |
|
3977 } |
|
3978 |
|
3979 /** |
|
3980 * Tests the empty pseudo-statement class. |
|
3981 */ |
|
3982 class DatabaseEmptyStatementTestCase extends DrupalWebTestCase { |
|
3983 public static function getInfo() { |
|
3984 return array( |
|
3985 'name' => 'Empty statement', |
|
3986 'description' => 'Test the empty pseudo-statement class.', |
|
3987 'group' => 'Database', |
|
3988 ); |
|
3989 } |
|
3990 |
|
3991 /** |
|
3992 * Test that the empty result set behaves as empty. |
|
3993 */ |
|
3994 function testEmpty() { |
|
3995 $result = new DatabaseStatementEmpty(); |
|
3996 |
|
3997 $this->assertTrue($result instanceof DatabaseStatementInterface, 'Class implements expected interface'); |
|
3998 $this->assertNull($result->fetchObject(), 'Null result returned.'); |
|
3999 } |
|
4000 |
|
4001 /** |
|
4002 * Test that the empty result set iterates safely. |
|
4003 */ |
|
4004 function testEmptyIteration() { |
|
4005 $result = new DatabaseStatementEmpty(); |
|
4006 |
|
4007 foreach ($result as $record) { |
|
4008 $this->fail('Iterating empty result set should not iterate.'); |
|
4009 return; |
|
4010 } |
|
4011 |
|
4012 $this->pass('Iterating empty result set skipped iteration.'); |
|
4013 } |
|
4014 |
|
4015 /** |
|
4016 * Test that the empty result set mass-fetches in an expected way. |
|
4017 */ |
|
4018 function testEmptyFetchAll() { |
|
4019 $result = new DatabaseStatementEmpty(); |
|
4020 |
|
4021 $this->assertEqual($result->fetchAll(), array(), 'Empty array returned from empty result set.'); |
|
4022 } |
|
4023 } |
|
4024 |
|
4025 /** |
|
4026 * Tests management of database connections. |
|
4027 */ |
|
4028 class ConnectionUnitTest extends DrupalUnitTestCase { |
|
4029 |
|
4030 protected $key; |
|
4031 protected $target; |
|
4032 |
|
4033 protected $monitor; |
|
4034 protected $originalCount; |
|
4035 |
|
4036 public static function getInfo() { |
|
4037 return array( |
|
4038 'name' => 'Connection unit tests', |
|
4039 'description' => 'Tests management of database connections.', |
|
4040 'group' => 'Database', |
|
4041 ); |
|
4042 } |
|
4043 |
|
4044 function setUp() { |
|
4045 parent::setUp(); |
|
4046 |
|
4047 $this->key = 'default'; |
|
4048 $this->originalTarget = 'default'; |
|
4049 $this->target = 'DatabaseConnectionUnitTest'; |
|
4050 |
|
4051 // Determine whether the database driver is MySQL. If it is not, the test |
|
4052 // methods will not be executed. |
|
4053 // @todo Make this test driver-agnostic, or find a proper way to skip it. |
|
4054 // @see http://drupal.org/node/1273478 |
|
4055 $connection_info = Database::getConnectionInfo('default'); |
|
4056 $this->skipTest = (bool) $connection_info['default']['driver'] != 'mysql'; |
|
4057 if ($this->skipTest) { |
|
4058 // Insert an assertion to prevent Simpletest from interpreting the test |
|
4059 // as failure. |
|
4060 $this->pass('This test is only compatible with MySQL.'); |
|
4061 } |
|
4062 |
|
4063 // Create an additional connection to monitor the connections being opened |
|
4064 // and closed in this test. |
|
4065 // @see TestBase::changeDatabasePrefix() |
|
4066 $connection_info = Database::getConnectionInfo('default'); |
|
4067 Database::addConnectionInfo('default', 'monitor', $connection_info['default']); |
|
4068 global $databases; |
|
4069 $databases['default']['monitor'] = $connection_info['default']; |
|
4070 $this->monitor = Database::getConnection('monitor'); |
|
4071 } |
|
4072 |
|
4073 /** |
|
4074 * Adds a new database connection info to Database. |
|
4075 */ |
|
4076 protected function addConnection() { |
|
4077 // Add a new target to the connection, by cloning the current connection. |
|
4078 $connection_info = Database::getConnectionInfo($this->key); |
|
4079 Database::addConnectionInfo($this->key, $this->target, $connection_info[$this->originalTarget]); |
|
4080 |
|
4081 // Verify that the new target exists. |
|
4082 $info = Database::getConnectionInfo($this->key); |
|
4083 // Note: Custom assertion message to not expose database credentials. |
|
4084 $this->assertIdentical($info[$this->target], $connection_info[$this->key], 'New connection info found.'); |
|
4085 } |
|
4086 |
|
4087 /** |
|
4088 * Returns the connection ID of the current test connection. |
|
4089 * |
|
4090 * @return integer |
|
4091 */ |
|
4092 protected function getConnectionID() { |
|
4093 return (int) Database::getConnection($this->target, $this->key)->query('SELECT CONNECTION_ID()')->fetchField(); |
|
4094 } |
|
4095 |
|
4096 /** |
|
4097 * Asserts that a connection ID exists. |
|
4098 * |
|
4099 * @param integer $id |
|
4100 * The connection ID to verify. |
|
4101 */ |
|
4102 protected function assertConnection($id) { |
|
4103 $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0); |
|
4104 return $this->assertTrue(isset($list[$id]), format_string('Connection ID @id found.', array('@id' => $id))); |
|
4105 } |
|
4106 |
|
4107 /** |
|
4108 * Asserts that a connection ID does not exist. |
|
4109 * |
|
4110 * @param integer $id |
|
4111 * The connection ID to verify. |
|
4112 */ |
|
4113 protected function assertNoConnection($id) { |
|
4114 $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0); |
|
4115 return $this->assertFalse(isset($list[$id]), format_string('Connection ID @id not found.', array('@id' => $id))); |
|
4116 } |
|
4117 |
|
4118 /** |
|
4119 * Tests Database::closeConnection() without query. |
|
4120 * |
|
4121 * @todo getConnectionID() executes a query. |
|
4122 */ |
|
4123 function testOpenClose() { |
|
4124 if ($this->skipTest) { |
|
4125 return; |
|
4126 } |
|
4127 // Add and open a new connection. |
|
4128 $this->addConnection(); |
|
4129 $id = $this->getConnectionID(); |
|
4130 Database::getConnection($this->target, $this->key); |
|
4131 |
|
4132 // Verify that there is a new connection. |
|
4133 $this->assertConnection($id); |
|
4134 |
|
4135 // Close the connection. |
|
4136 Database::closeConnection($this->target, $this->key); |
|
4137 // Wait 20ms to give the database engine sufficient time to react. |
|
4138 usleep(20000); |
|
4139 |
|
4140 // Verify that we are back to the original connection count. |
|
4141 $this->assertNoConnection($id); |
|
4142 } |
|
4143 |
|
4144 /** |
|
4145 * Tests Database::closeConnection() with a query. |
|
4146 */ |
|
4147 function testOpenQueryClose() { |
|
4148 if ($this->skipTest) { |
|
4149 return; |
|
4150 } |
|
4151 // Add and open a new connection. |
|
4152 $this->addConnection(); |
|
4153 $id = $this->getConnectionID(); |
|
4154 Database::getConnection($this->target, $this->key); |
|
4155 |
|
4156 // Verify that there is a new connection. |
|
4157 $this->assertConnection($id); |
|
4158 |
|
4159 // Execute a query. |
|
4160 Database::getConnection($this->target, $this->key)->query('SHOW TABLES'); |
|
4161 |
|
4162 // Close the connection. |
|
4163 Database::closeConnection($this->target, $this->key); |
|
4164 // Wait 20ms to give the database engine sufficient time to react. |
|
4165 usleep(20000); |
|
4166 |
|
4167 // Verify that we are back to the original connection count. |
|
4168 $this->assertNoConnection($id); |
|
4169 } |
|
4170 |
|
4171 /** |
|
4172 * Tests Database::closeConnection() with a query and custom prefetch method. |
|
4173 */ |
|
4174 function testOpenQueryPrefetchClose() { |
|
4175 if ($this->skipTest) { |
|
4176 return; |
|
4177 } |
|
4178 // Add and open a new connection. |
|
4179 $this->addConnection(); |
|
4180 $id = $this->getConnectionID(); |
|
4181 Database::getConnection($this->target, $this->key); |
|
4182 |
|
4183 // Verify that there is a new connection. |
|
4184 $this->assertConnection($id); |
|
4185 |
|
4186 // Execute a query. |
|
4187 Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol(); |
|
4188 |
|
4189 // Close the connection. |
|
4190 Database::closeConnection($this->target, $this->key); |
|
4191 // Wait 20ms to give the database engine sufficient time to react. |
|
4192 usleep(20000); |
|
4193 |
|
4194 // Verify that we are back to the original connection count. |
|
4195 $this->assertNoConnection($id); |
|
4196 } |
|
4197 |
|
4198 /** |
|
4199 * Tests Database::closeConnection() with a select query. |
|
4200 */ |
|
4201 function testOpenSelectQueryClose() { |
|
4202 if ($this->skipTest) { |
|
4203 return; |
|
4204 } |
|
4205 // Add and open a new connection. |
|
4206 $this->addConnection(); |
|
4207 $id = $this->getConnectionID(); |
|
4208 Database::getConnection($this->target, $this->key); |
|
4209 |
|
4210 // Verify that there is a new connection. |
|
4211 $this->assertConnection($id); |
|
4212 |
|
4213 // Create a table. |
|
4214 $name = 'foo'; |
|
4215 Database::getConnection($this->target, $this->key)->schema()->createTable($name, array( |
|
4216 'fields' => array( |
|
4217 'name' => array( |
|
4218 'type' => 'varchar', |
|
4219 'length' => 255, |
|
4220 ), |
|
4221 ), |
|
4222 )); |
|
4223 |
|
4224 // Execute a query. |
|
4225 Database::getConnection($this->target, $this->key)->select('foo', 'f') |
|
4226 ->fields('f', array('name')) |
|
4227 ->execute() |
|
4228 ->fetchAll(); |
|
4229 |
|
4230 // Drop the table. |
|
4231 Database::getConnection($this->target, $this->key)->schema()->dropTable($name); |
|
4232 |
|
4233 // Close the connection. |
|
4234 Database::closeConnection($this->target, $this->key); |
|
4235 // Wait 20ms to give the database engine sufficient time to react. |
|
4236 usleep(20000); |
|
4237 |
|
4238 // Verify that we are back to the original connection count. |
|
4239 $this->assertNoConnection($id); |
|
4240 } |
|
4241 |
|
4242 } |