Merge pull request #14639 from colemanw/i18nCleanup
[civicrm-core.git] / tests / phpunit / CRM / Core / DAOTest.php
CommitLineData
71e5aa5c
ARW
1<?php
2
aba1cd8b
EM
3/**
4 * Class CRM_Core_DAOTest
acb109b7 5 * @group headless
aba1cd8b 6 */
71e5aa5c 7class CRM_Core_DAOTest extends CiviUnitTestCase {
71e5aa5c 8
9e0f668c
TO
9 const ABORTED_SQL = "_aborted_sql_";
10
00be9182 11 public function testGetReferenceColumns() {
71e5aa5c
ARW
12 // choose CRM_Core_DAO_Email as an arbitrary example
13 $emailRefs = CRM_Core_DAO_Email::getReferenceColumns();
14 $refsByTarget = array();
15 foreach ($emailRefs as $refSpec) {
16 $refsByTarget[$refSpec->getTargetTable()] = $refSpec;
17 }
18 $this->assertTrue(array_key_exists('civicrm_contact', $refsByTarget));
19 $contactRef = $refsByTarget['civicrm_contact'];
20 $this->assertEquals('contact_id', $contactRef->getReferenceKey());
21 $this->assertEquals('id', $contactRef->getTargetKey());
11626cf1 22 $this->assertEquals('CRM_Core_Reference_Basic', get_class($contactRef));
71e5aa5c
ARW
23 }
24
00be9182 25 public function testGetReferencesToTable() {
71e5aa5c
ARW
26 $refs = CRM_Core_DAO::getReferencesToTable(CRM_Financial_DAO_FinancialType::getTableName());
27 $refsBySource = array();
28 foreach ($refs as $refSpec) {
29 $refsBySource[$refSpec->getReferenceTable()] = $refSpec;
30 }
31 $this->assertTrue(array_key_exists('civicrm_entity_financial_account', $refsBySource));
32 $genericRef = $refsBySource['civicrm_entity_financial_account'];
33 $this->assertEquals('entity_id', $genericRef->getReferenceKey());
34 $this->assertEquals('entity_table', $genericRef->getTypeColumn());
35 $this->assertEquals('id', $genericRef->getTargetKey());
11626cf1 36 $this->assertEquals('CRM_Core_Reference_Dynamic', get_class($genericRef));
71e5aa5c
ARW
37 }
38
00be9182 39 public function testFindReferences() {
71e5aa5c
ARW
40 $params = array(
41 'first_name' => 'Testy',
42 'last_name' => 'McScallion',
43 'contact_type' => 'Individual',
44 );
45
46 $contact = CRM_Contact_BAO_Contact::add($params);
47 $this->assertNotNull($contact->id);
48
49 $params = array(
50 'email' => 'spam@dev.null',
51 'contact_id' => $contact->id,
52 'is_primary' => 0,
53 'location_type_id' => 1,
54 );
55
56 $email = CRM_Core_BAO_Email::add($params);
57
58 $refs = $contact->findReferences();
59 $refsByTable = array();
60 foreach ($refs as $refObj) {
61 $refsByTable[$refObj->__table] = $refObj;
62 }
63
64 $this->assertTrue(array_key_exists('civicrm_email', $refsByTable));
65 $refDao = $refsByTable['civicrm_email'];
66 $refDao->find(TRUE);
67 $this->assertEquals($contact->id, $refDao->contact_id);
68 }
c8950569 69
4cbe18b8
EM
70 /**
71 * @return array
72 */
00be9182 73 public function composeQueryExamples() {
0c627c6f
TO
74 $cases = array();
75 // $cases[] = array('Input-SQL', 'Input-Params', 'Expected-SQL');
76
9e0f668c
TO
77 $cases[0] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('', 'String')), 'UPDATE civicrm_foo SET bar = \'\'');
78 $cases[1] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('the text', 'String')), 'UPDATE civicrm_foo SET bar = \'the text\'');
79 $cases[2] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array(NULL, 'String')), self::ABORTED_SQL);
80 $cases[3] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('null', 'String')), 'UPDATE civicrm_foo SET bar = NULL');
81
82 $cases[3] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('', 'Float')), self::ABORTED_SQL);
83 $cases[4] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('1.23', 'Float')), 'UPDATE civicrm_foo SET bar = 1.23');
84 $cases[5] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array(NULL, 'Float')), self::ABORTED_SQL);
85 $cases[6] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('null', 'Float')), self::ABORTED_SQL);
86
87 $cases[11] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('', 'Money')), self::ABORTED_SQL);
88 $cases[12] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('1.23', 'Money')), 'UPDATE civicrm_foo SET bar = 1.23');
89 $cases[13] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array(NULL, 'Money')), self::ABORTED_SQL);
90 $cases[14] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('null', 'Money')), self::ABORTED_SQL);
91
92 $cases[15] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('', 'Int')), self::ABORTED_SQL);
93 $cases[16] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('123', 'Int')), 'UPDATE civicrm_foo SET bar = 123');
94 $cases[17] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array(NULL, 'Int')), self::ABORTED_SQL);
95 $cases[18] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('null', 'Int')), self::ABORTED_SQL);
96
97 $cases[19] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('', 'Timestamp')), 'UPDATE civicrm_foo SET bar = null');
98 $cases[20] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('20150102030405', 'Timestamp')), 'UPDATE civicrm_foo SET bar = 20150102030405');
99 $cases[21] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array(NULL, 'Timestamp')), 'UPDATE civicrm_foo SET bar = null');
100 $cases[22] = array('UPDATE civicrm_foo SET bar = %1', array(1 => array('null', 'Timestamp')), self::ABORTED_SQL);
101
0c627c6f 102 // CASE: No params
9e0f668c 103 $cases[1000] = array(
0c627c6f
TO
104 'SELECT * FROM whatever',
105 array(),
106 'SELECT * FROM whatever',
107 );
108
109 // CASE: Integer param
9e0f668c 110 $cases[1001] = array(
0c627c6f
TO
111 'SELECT * FROM whatever WHERE id = %1',
112 array(
113 1 => array(10, 'Integer'),
114 ),
115 'SELECT * FROM whatever WHERE id = 10',
116 );
117
118 // CASE: String param
9e0f668c 119 $cases[1002] = array(
0c627c6f
TO
120 'SELECT * FROM whatever WHERE name = %1',
121 array(
122 1 => array('Alice', 'String'),
123 ),
124 'SELECT * FROM whatever WHERE name = \'Alice\'',
125 );
126
127 // CASE: Two params
9e0f668c 128 $cases[1003] = array(
0c627c6f
TO
129 'SELECT * FROM whatever WHERE name = %1 AND title = %2',
130 array(
131 1 => array('Alice', 'String'),
132 2 => array('Bob', 'String'),
133 ),
134 'SELECT * FROM whatever WHERE name = \'Alice\' AND title = \'Bob\'',
135 );
136
137 // CASE: Two params with special character (%1)
9e0f668c 138 $cases[1004] = array(
0c627c6f
TO
139 'SELECT * FROM whatever WHERE name = %1 AND title = %2',
140 array(
141 1 => array('Alice %2', 'String'),
142 2 => array('Bob', 'String'),
143 ),
144 'SELECT * FROM whatever WHERE name = \'Alice %2\' AND title = \'Bob\'',
145 );
146
147 // CASE: Two params with special character ($1)
9e0f668c 148 $cases[1005] = array(
0c627c6f
TO
149 'SELECT * FROM whatever WHERE name = %1 AND title = %2',
150 array(
151 1 => array('Alice $1', 'String'),
152 2 => array('Bob', 'String'),
153 ),
154 'SELECT * FROM whatever WHERE name = \'Alice $1\' AND title = \'Bob\'',
155 );
156
157 return $cases;
158 }
159
160 /**
161 * @dataProvider composeQueryExamples
1e1fdcf6
EM
162 * @param $inputSql
163 * @param $inputParams
164 * @param $expectSql
0c627c6f 165 */
00be9182 166 public function testComposeQuery($inputSql, $inputParams, $expectSql) {
9e0f668c
TO
167 $scope = CRM_Core_TemporaryErrorScope::useException();
168 try {
169 $actualSql = CRM_Core_DAO::composeQuery($inputSql, $inputParams);
170 }
171 catch (Exception $e) {
172 $actualSql = self::ABORTED_SQL;
173 }
0c627c6f
TO
174 $this->assertEquals($expectSql, $actualSql);
175 }
e2508c90 176
0eea664b
TO
177 /**
178 * CASE: Two params where the %2 is already present in the query
179 * NOTE: This case should rightly FAIL, as using strstr in the replace mechanism will turn
180 * the query into: SELECT * FROM whatever WHERE name = 'Alice' AND title = 'Bob' AND year LIKE ''Bob'012'
181 * So, to avoid such ERROR, the query should be framed like:
182 * 'SELECT * FROM whatever WHERE name = %1 AND title = %3 AND year LIKE '%2012'
183 * $params[3] = array('Bob', 'String');
184 * i.e. the place holder should be unique and should not contain in any other operational use in query
185 */
00be9182 186 public function testComposeQueryFailure() {
e2508c90
PJ
187 $cases[] = array(
188 'SELECT * FROM whatever WHERE name = %1 AND title = %2 AND year LIKE \'%2012\' ',
189 array(
190 1 => array('Alice', 'String'),
191 2 => array('Bob', 'String'),
192 ),
193 'SELECT * FROM whatever WHERE name = \'Alice\' AND title = \'Bob\' AND year LIKE \'%2012\' ',
194 );
195 list($inputSql, $inputParams, $expectSql) = $cases[0];
196 $actualSql = CRM_Core_DAO::composeQuery($inputSql, $inputParams);
197 $this->assertFalse(($expectSql == $actualSql));
9e0f668c 198 unset($scope);
e2508c90 199 }
6842bb53 200
4cbe18b8
EM
201 /**
202 * @return array
203 */
00be9182 204 public function sqlNameDataProvider() {
6842bb53
DL
205 return array(
206 array('this is a long string', 30, FALSE, 'this is a long string'),
92915c55
TO
207 array(
208 'this is an even longer string which is exactly 60 character',
209 60,
210 FALSE,
0eea664b 211 'this is an even longer string which is exactly 60 character',
92915c55
TO
212 ),
213 array(
214 'this is an even longer string which is exactly 60 character',
215 60,
216 TRUE,
0eea664b 217 'this is an even longer string which is exactly 60 character',
92915c55
TO
218 ),
219 array(
220 'this is an even longer string which is a bit more than 60 character',
221 60,
222 FALSE,
0eea664b 223 'this is an even longer string which is a bit more than 60 ch',
92915c55
TO
224 ),
225 array(
226 'this is an even longer string which is a bit more than 60 character',
227 60,
228 TRUE,
0eea664b 229 'this is an even longer string which is a bit more th_c1cbd519',
92915c55 230 ),
6842bb53
DL
231 );
232 }
233
234 /**
235 * @dataProvider sqlNameDataProvider
1e1fdcf6
EM
236 * @param $inputData
237 * @param $length
238 * @param $makeRandom
239 * @param $expectedResult
6842bb53 240 */
00be9182 241 public function testShortenSQLName($inputData, $length, $makeRandom, $expectedResult) {
6842bb53
DL
242 $this->assertEquals($expectedResult, CRM_Core_DAO::shortenSQLName($inputData, $length, $makeRandom));
243 }
244
00be9182 245 public function testFindById() {
2c0a1ab8
AN
246 $params = $this->sampleContact('Individual', 4);
247 $existing_contact = CRM_Contact_BAO_Contact::add($params);
248 $contact = CRM_Contact_BAO_Contact::findById($existing_contact->id);
249 $this->assertEquals($existing_contact->id, $contact->id);
250 $deleted_contact_id = $existing_contact->id;
251 CRM_Contact_BAO_Contact::deleteContact($contact->id, FALSE, TRUE);
252 $exception_thrown = FALSE;
253 try {
254 $deleted_contact = CRM_Contact_BAO_Contact::findById($deleted_contact_id);
255 }
256 catch (Exception $e) {
257 $exception_thrown = TRUE;
258 }
259 $this->assertTrue($exception_thrown);
260 }
0eea664b 261
fd542660 262 /**
ee17d64d 263 * requireSafeDBName() method (to check valid database name)
fd542660 264 */
ee17d64d 265 public function testRequireSafeDBName() {
fd542660
PN
266 $databases = array(
267 'testdb' => TRUE,
268 'test_db' => TRUE,
269 'TEST_db' => TRUE,
270 '123testdb' => TRUE,
271 'test12db34' => TRUE,
272 'test_12_db34' => TRUE,
ee17d64d 273 'test-db' => TRUE,
fd542660
PN
274 'test;db' => FALSE,
275 'test*&db' => FALSE,
276 'testdb;Delete test' => FALSE,
277 '123456' => FALSE,
278 'test#$%^&*' => FALSE,
279 );
280 $testDetails = array();
281 foreach ($databases as $database => $val) {
ee17d64d 282 $this->assertEquals(CRM_Core_DAO::requireSafeDBName($database), $val);
fd542660
PN
283 }
284 }
285
3fa9688a 286 /**
287 * Test the function designed to find myIsam tables.
288 */
289 public function testMyISAMCheck() {
2475b550
TO
290 // Cleanup previous, failed tests.
291 CRM_Core_DAO::executeQuery('DROP TABLE IF EXISTS civicrm_my_isam');
292
293 // A manually created MyISAM table should raise a redflag.
3fa9688a 294 $this->assertEquals(0, CRM_Core_DAO::isDBMyISAM());
295 CRM_Core_DAO::executeQuery('CREATE TABLE civicrm_my_isam (`id` int(10) unsigned NOT NULL) ENGINE = MyISAM');
296 $this->assertEquals(1, CRM_Core_DAO::isDBMyISAM());
297 CRM_Core_DAO::executeQuery('DROP TABLE civicrm_my_isam');
2475b550
TO
298
299 // A temp table should not raise flag (static naming).
300 $tempName = CRM_Core_DAO::createTempTableName('civicrm', FALSE);
301 $this->assertEquals(0, CRM_Core_DAO::isDBMyISAM());
302 CRM_Core_DAO::executeQuery("CREATE TABLE $tempName (`id` int(10) unsigned NOT NULL) ENGINE = MyISAM");
39b959db
SL
303 // Ignore temp tables
304 $this->assertEquals(0, CRM_Core_DAO::isDBMyISAM());
2475b550
TO
305 CRM_Core_DAO::executeQuery("DROP TABLE $tempName");
306
307 // A temp table should not raise flag (randomized naming).
308 $tempName = CRM_Core_DAO::createTempTableName('civicrm', TRUE);
309 $this->assertEquals(0, CRM_Core_DAO::isDBMyISAM());
310 CRM_Core_DAO::executeQuery("CREATE TABLE $tempName (`id` int(10) unsigned NOT NULL) ENGINE = MyISAM");
39b959db
SL
311 // Ignore temp tables
312 $this->assertEquals(0, CRM_Core_DAO::isDBMyISAM());
2475b550 313 CRM_Core_DAO::executeQuery("DROP TABLE $tempName");
3fa9688a 314 }
315
e1047fac 316 /**
317 * CRM-19930: Test toArray() function with $format param
318 */
319 public function testDAOtoArray() {
320 $format = 'user[%s]';
321 $params = array(
322 'first_name' => 'Testy',
323 'last_name' => 'McScallion',
324 'contact_type' => 'Individual',
325 );
326
327 $dao = CRM_Contact_BAO_Contact::add($params);
328 $query = "SELECT contact_type, display_name FROM civicrm_contact WHERE id={$dao->id}";
329 $toArray = array(
330 'contact_type' => 'Individual',
331 'display_name' => 'Testy McScallion',
332 );
333 $modifiedKeyArray = array();
334 foreach ($toArray as $k => $v) {
335 $modifiedKeyArray[sprintf($format, $k)] = $v;
336 }
337
338 $dao = CRM_Core_DAO::executeQuery($query);
339 while ($dao->fetch()) {
340 $daoToArray = $dao->toArray();
341 $this->checkArrayEquals($toArray, $daoToArray);
342 $daoToArray = $dao->toArray($format);
343 $this->checkArrayEquals($modifiedKeyArray, $daoToArray);
344 }
345 }
346
4d1368d8 347 /**
348 * CRM-17748: Test internal DAO options
349 */
350 public function testDBOptions() {
351 $contactIDs = array();
352 for ($i = 0; $i < 10; $i++) {
353 $contactIDs[] = $this->individualCreate(array(
354 'first_name' => 'Alan' . substr(sha1(rand()), 0, 7),
355 'last_name' => 'Smith' . substr(sha1(rand()), 0, 4),
356 ));
357 }
358
359 // Test option 'result_buffering'
360 $this->_testMemoryUsageForUnbufferedQuery();
361
362 // cleanup
363 foreach ($contactIDs as $contactID) {
364 $this->callAPISuccess('Contact', 'delete', array('id' => $contactID));
365 }
366 }
367
368 /**
369 * Helper function to test result of buffered and unbuffered query
370 */
371 public function _testMemoryUsageForUnbufferedQuery() {
372 $sql = "SELECT * FROM civicrm_contact WHERE first_name LIKE 'Alan%' AND last_name LIKE 'Smith%' ";
373
374 $dao = CRM_Core_DAO::executeQuery($sql);
375 $contactsFetchedFromBufferedQuery = $dao->fetchAll();
4d1368d8 376
377 $dao = CRM_Core_DAO::executeUnbufferedQuery($sql);
378 $contactsFetchedFromUnbufferedQuery = $dao->fetchAll();
4d1368d8 379
380 $this->checkArrayEquals($contactsFetchedFromBufferedQuery, $contactsFetchedFromUnbufferedQuery);
381 }
382
403b1c11
SL
383 /**
384 * Test that known sql modes are present in session.
385 */
386 public function testSqlModePresent() {
712e729f 387 $sqlModes = CRM_Utils_SQL::getSqlModes();
403b1c11
SL
388 // assert we have strict trans
389 $this->assertContains('STRICT_TRANS_TABLES', $sqlModes);
7bb0eb64 390 if (CRM_Utils_SQL::supportsFullGroupBy()) {
403b1c11
SL
391 $this->assertContains('ONLY_FULL_GROUP_BY', $sqlModes);
392 }
393 }
394
2a5c9b4d
CW
395 /**
396 * @return array
397 */
398 public function serializationMethods() {
399 $constants = array();
400 $simpleData = array(
401 NULL,
402 array('Foo', 'Bar', '3', '4', '5'),
403 array(),
404 array('0'),
405 );
406 $complexData = array(
407 array(
408 'foo' => 'bar',
409 'baz' => array('1', '2', '3', array('one', 'two')),
410 '3' => '0',
411 ),
412 );
413 $daoInfo = new ReflectionClass('CRM_Core_DAO');
414 foreach ($daoInfo->getConstants() as $constant => $val) {
415 if ($constant == 'SERIALIZE_JSON' || $constant == 'SERIALIZE_PHP') {
416 $constants[] = array($val, array_merge($simpleData, $complexData));
417 }
418 elseif (strpos($constant, 'SERIALIZE_') === 0) {
419 $constants[] = array($val, $simpleData);
420 }
421 }
422 return $constants;
423 }
424
41d41c91
TO
425 public function testFetchGeneratorDao() {
426 $this->individualCreate([], 0);
427 $this->individualCreate([], 1);
428 $this->individualCreate([], 2);
429 $count = 0;
430 $g = CRM_Core_DAO::executeQuery('SELECT contact_type FROM civicrm_contact WHERE contact_type = "Individual" LIMIT 3')
431 ->fetchGenerator();
432 foreach ($g as $row) {
433 $this->assertEquals('Individual', $row->contact_type);
434 $count++;
435 }
436 $this->assertEquals(3, $count);
437 }
438
439 public function testFetchGeneratorArray() {
440 $this->individualCreate([], 0);
441 $this->individualCreate([], 1);
442 $this->individualCreate([], 2);
443 $count = 0;
444 $g = CRM_Core_DAO::executeQuery('SELECT contact_type FROM civicrm_contact WHERE contact_type = "Individual" LIMIT 3')
445 ->fetchGenerator('array');
446 foreach ($g as $row) {
447 $this->assertEquals('Individual', $row['contact_type']);
448 $count++;
449 }
450 $this->assertEquals(3, $count);
451 }
452
2a5c9b4d
CW
453 /**
454 * @dataProvider serializationMethods
455 */
456 public function testFieldSerialization($method, $sampleData) {
457 foreach ($sampleData as $value) {
458 $serialized = CRM_Core_DAO::serializeField($value, $method);
459 $newValue = CRM_Core_DAO::unSerializeField($serialized, $method);
460 $this->assertEquals($value, $newValue);
461 }
462 }
463
ffcc1d11 464 /**
465 * Test the DAO cloning method does not hit issues with freeing the result.
466 */
467 public function testCloneDAO() {
468 $dao = CRM_Core_DAO::executeQuery('SELECT * FROM civicrm_domain');
469 $i = 0;
470 while ($dao->fetch()) {
471 $i++;
472 $cloned = clone($dao);
473 unset($cloned);
474 }
475 $this->assertEquals(2, $i);
476 }
477
feb7e5d4 478 /**
479 * Test modifying a query in a hook.
480 *
481 * Test that adding a sensible string does not cause failure.
482 *
483 * @throws \Exception
484 */
485 public function testModifyQuery() {
486 $listener = function(\Civi\Core\Event\GenericHookEvent $e) {
487 $e->query = '/* User : hooked */' . $e->query;
488 };
489 Civi::dispatcher()->addListener('civi.db.query', $listener);
490 CRM_Core_DAO::executeQuery('SELECT * FROM civicrm_domain');
491
492 Civi::dispatcher()->removeListener('civi.db.query', $listener);
493 }
494
495 /**
496 * Test modifying a query in a hook.
497 *
498 * Demonstrate it is modified showing the query now breaks.
499 */
500 public function testModifyAndBreakQuery() {
501 $listener = function(\Civi\Core\Event\GenericHookEvent $e) {
502 $e->query = '/* Forgot trailing comment marker' . $e->query;
503 };
504 Civi::dispatcher()->addListener('civi.db.query', $listener);
505 try {
506 CRM_Core_DAO::executeQuery('SELECT * FROM civicrm_domain');
507 }
508 catch (PEAR_Exception $e) {
509 $this->assertEquals(
510 "SELECT * FROM civicrm_domain [nativecode=1064 ** You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '/* Forgot trailing comment markerSELECT * FROM civicrm_domain' at line 1]",
511 $e->getCause()->getUserInfo()
512 );
513 Civi::dispatcher()->removeListener('civi.db.query', $listener);
514 return;
515 }
516 Civi::dispatcher()->removeListener('civi.db.query', $listener);
517 $this->fail('String not altered');
518 }
519
71e5aa5c 520}