sqlsrv.select.test 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <?php
  2. /**
  3. * @file
  4. * Support tests for SQL Server.
  5. */
  6. class SqlServerSelectTest extends DatabaseTestCase {
  7. /**
  8. * {inheritdoc}
  9. */
  10. public static function getInfo() {
  11. return array(
  12. 'name' => 'Select tests',
  13. 'description' => 'Extended tests for SELECT handling.',
  14. 'group' => 'Database (SQL Server)',
  15. );
  16. }
  17. /**
  18. * Test group by expansion.
  19. */
  20. public function testGroupByExpansion() {
  21. // By ANSI SQL, GROUP BY columns cannot use aliases. Test that the
  22. // driver expands the aliases properly.
  23. $query = db_select('test_task', 't');
  24. $count_field = $query->addExpression('COUNT(task)', 'num');
  25. $task_field = $query->addExpression('CONCAT(:prefix, t.task)', 'task', array(':prefix' => 'Task: '));
  26. $query->orderBy($count_field);
  27. $query->groupBy($task_field);
  28. $result = $query->execute();
  29. $num_records = 0;
  30. $last_count = 0;
  31. $records = array();
  32. foreach ($result as $record) {
  33. $num_records++;
  34. $this->assertTrue($record->$count_field >= $last_count, t('Results returned in correct order.'));
  35. $last_count = $record->$count_field;
  36. $records[$record->$task_field] = $record->$count_field;
  37. }
  38. $correct_results = array(
  39. 'Task: eat' => 1,
  40. 'Task: sleep' => 2,
  41. 'Task: code' => 1,
  42. 'Task: found new band' => 1,
  43. 'Task: perform at superbowl' => 1,
  44. );
  45. foreach ($correct_results as $task => $count) {
  46. $this->assertEqual($records[$task], $count, t("Correct number of '@task' records found.", array('@task' => $task)));
  47. }
  48. $this->assertEqual($num_records, 6, t('Returned the correct number of total rows.'));
  49. // The driver is also verifying that any expressions/columns used in GROUP BY/ORDER BY
  50. // are added to the select list if they are not because SQL Server will complain
  51. // about those fields missing in aggregates.
  52. $query = db_select('test_task', 't')->distinct();
  53. $query->addField('t', 'task');
  54. $query->orderBy('t.pid');
  55. try {
  56. $result = $query->execute();
  57. }
  58. catch (\Exception $e) {
  59. $this->assert('fail', "Driver failed to deal with missing fields in aggregate: {$e->getMessage()}");
  60. }
  61. $query = db_select('test_task', 't');
  62. $query->addField('t', 'task');
  63. $query->orderBy('t.pid');
  64. $query->groupBy('t.pid');
  65. try {
  66. $result = $query->execute();
  67. }
  68. catch (\Exception $e) {
  69. $this->assert('fail', "Driver failed to deal with missing fields in aggregate: {$e->getMessage()}");
  70. }
  71. }
  72. /**
  73. * Test cross join.
  74. */
  75. public function testCrossJoin() {
  76. // SelectQuery in SQL Server driver
  77. // is expanding expressions into a cross
  78. // join statement. This allows the use
  79. // of these expressions in the Aggregate
  80. // or Where part of the query.
  81. $query = db_select('test_task', 't');
  82. // Cast the task to an accent insensitive collation in an expression.
  83. $query->addExpression('(t.task collate Latin1_General_CS_AI)', 'ci_task');
  84. // Add condition over that expression.
  85. $query->where('ci_task = :param', array(':param' => 'slëep'));
  86. $result = $query->execute();
  87. $num_records = 0;
  88. foreach ($result as $record) {
  89. $num_records++;
  90. }
  91. $this->assertEqual($num_records, 2, t('Returned the correct number of total rows.'));
  92. // There is a special case, if the query is an aggregate
  93. // and an expression is used, this expression must be part of the aggregate.
  94. $query = db_select('test_task', 't');
  95. // Cast the task to an accent insensitive collation in an expression.
  96. $query->addExpression('(t.task collate Latin1_General_CS_AI)', 'ci_task');
  97. // Add condition over that expression.
  98. $query->where('ci_task = :param', array(':param' => 'slëep'));
  99. // Add condition over that expression.
  100. $query->groupBy('t.task');
  101. $result = $query->execute();
  102. $num_records = 0;
  103. foreach ($result as $record) {
  104. $num_records++;
  105. }
  106. $this->assertEqual($num_records, 1, t('Returned the correct number of total rows.'));
  107. }
  108. /**
  109. * Test the 2100 parameter limit per query.
  110. */
  111. public function testParameterLimit() {
  112. $values = array();
  113. for ($x = 0; $x < 2200; $x ++) {
  114. $values[] = uniqid($x, TRUE);
  115. }
  116. $query = db_select('test_task', 't');
  117. $query->addExpression('COUNT(task)', 'num');
  118. $query->where('t.task IN (:data)', array(':data' => $values));
  119. $result = NULL;
  120. // If > 2100 we can get SQL Exception! The driver must handle that.
  121. try {
  122. $result = $query->execute()->fetchField();
  123. }
  124. catch (\Exception $err) {
  125. }
  126. $this->assertEqual($result, 0, 'Returned the correct number of total rows.');
  127. }
  128. /**
  129. * Although per official documentation you cannot send
  130. * duplicate placeholders in same query, this works in mySQL
  131. * and is present in some queries, even in core, wich have not
  132. * gotten enough attention.
  133. */
  134. public function testDuplicatePlaceholders() {
  135. $query = db_select('test_task', 't');
  136. $query->addExpression('COUNT(task)', 'num');
  137. $query->where('t.task IN (:data0, :data0)', array(':data0' => 'sleep'));
  138. $result = NULL;
  139. try {
  140. $result = $query->execute()->fetchField();
  141. }
  142. catch (\Exception $err) {
  143. }
  144. $this->assertEqual($result, 2, 'Returned the correct number of total rows.');
  145. }
  146. /**
  147. * Test for weird key names
  148. * in array arguments.
  149. */
  150. public function testBadKeysInArrayArguments() {
  151. $params[':nids'] = array(
  152. 'uid1' => -9,
  153. 'What a bad placeholder name, why should we care?' => -6,
  154. );
  155. $result = NULL;
  156. try {
  157. // The regular expandArguments implementation will fail to
  158. // properly expand the associative array with weird keys, OH, and actually
  159. // you can perform some SQL Injection through the array keys.
  160. $result = db_query('SELECT COUNT(*) FROM USERS WHERE USERS.UID IN (:nids)', $params)->fetchField();
  161. }
  162. catch (\Exception $err) {
  163. // Regular drupal will fail with
  164. // SQLSTATE[IMSSP]: An error occurred substituting the named parameters.
  165. // https://www.drupal.org/node/2146839
  166. }
  167. // User ID's are negative, so this should return 0 matches.
  168. $this->assertEqual($result, 0, 'Returned the correct number of total rows.');
  169. }
  170. /**
  171. * Test the temporary table functionality.
  172. */
  173. public function testTemporaryTables() {
  174. $query = db_select('test_task', 't');
  175. $query->fields('t');
  176. $table = db_query_temporary((string) $query);
  177. // First assert that the table exists
  178. $this->assertTRUE(db_table_exists($table), 'The temporary table exists.');
  179. $query2 = db_select($table, 't');
  180. $query2->fields('t');
  181. // Now make sure that both tables are exactly the same.
  182. $data1 = $query->execute()->fetchAllAssoc('tid');
  183. $data2 = $query2->execute()->fetchAllAssoc('tid');
  184. // User ID's are negative, so this should return 0 matches.
  185. $this->assertEqual(count($data1), count($data2), 'Temporary table has the same number of rows.');
  186. // $this->assertEqual(count($data1[0]), count($data2[0]), 'Temporary table has the same number of columns.');
  187. // Drop the table.
  188. db_drop_table($table);
  189. // The table should not exist now.
  190. $this->assertFALSE(db_table_exists($table), 'The temporary table does not exists.');
  191. }
  192. /**
  193. * Test LIKE statement wildcards are properly escaped.
  194. */
  195. public function testEscapeLike() {
  196. // Test expected escaped characters
  197. $string = 't[e%s]t_\\';
  198. $expected = 't[[]e[%]s[]]t[_]\\';
  199. $actual = db_like($string);
  200. $this->assertEqual($actual, $expected, 'Properly escaped LIKE statement wildcards.');
  201. db_insert('test_task')
  202. ->fields(array(
  203. 'task' => 'T\\est',
  204. ))
  205. ->execute();
  206. $query = db_select('test_task', 't');
  207. $query->fields('t');
  208. $query->condition('t.task', db_like('T\\est'), 'LIKE');
  209. $result = $query->execute()->fetchAll();
  210. $this->assertEqual(count($result), 1, t('db_select returned the correct number of total rows.'));
  211. db_insert('test_task')
  212. ->fields(array(
  213. 'task' => 'T\'est',
  214. ))
  215. ->execute();
  216. $query = db_select('test_task', 't');
  217. $query->fields('t');
  218. $query->condition('t.task', db_like('T\'est'), 'LIKE');
  219. $result = $query->execute()->fetchAll();
  220. $this->assertEqual(count($result), 1, t('db_select returned the correct number of total rows.'));
  221. // db_select: Test unescaped wildcard.
  222. $query = db_select('test_task', 't');
  223. $query->condition('t.task', '[s]leep', 'LIKE');
  224. $query->fields('t');
  225. $result = $query->execute()->fetchAll();
  226. $this->assertEqual(count($result), 2, t('db_select returned the correct number of total rows.'));
  227. // db_select: Test unescaped wildcard.
  228. $query = db_select('test_task', 't');
  229. $query->condition('t.task', '[s]leep', 'LIKE');
  230. $query->fields('t');
  231. $result = $query->execute()->fetchAll();
  232. $this->assertEqual(count($result), 2, t('db_select returned the correct number of total rows.'));
  233. // db_select: Test escaped wildcard.
  234. $query = db_select('test_task', 't');
  235. $query->condition('t.task', db_like('[s]leep'), 'LIKE');
  236. $query->fields('t');
  237. $result = $query->execute()->fetchAll();
  238. $this->assertEqual(count($result), 0, t('db_select returned the correct number of total rows.'));
  239. // db_select->where: Test unescaped wildcard.
  240. $query = db_select('test_task', 't');
  241. $query->where('t.task LIKE :task', array(':task' => '[s]leep'));
  242. $query->fields('t');
  243. $result = $query->execute()->fetchAll();
  244. $this->assertEqual(count($result), 2, t('db_select returned the correct number of total rows.'));
  245. // db_select->where: Test escaped wildcard.
  246. $query = db_select('test_task', 't');
  247. $query->where('t.task LIKE :task', array(':task' => db_like('[s]leep')));
  248. $query->fields('t');
  249. $result = $query->execute()->fetchAll();
  250. $this->assertEqual(count($result), 0, t('db_select returned the correct number of total rows.'));
  251. // db_query: Test unescaped wildcard.
  252. $query = db_query('SELECT COUNT(*) FROM {test_task} WHERE task LIKE :task',
  253. array(':task' => '[s]leep'));
  254. $result = $query->fetchField();
  255. $this->assertEqual($result, 2, t('db_query returned the correct number of total rows.'));
  256. // db_query: Test escaped wildcard.
  257. $query = db_query('SELECT COUNT(*) FROM {test_task} WHERE task LIKE :task',
  258. array(':task' => db_like('[s]leep')));
  259. $result = $query->fetchField();
  260. $this->assertEqual($result, 0, t('db_query returned the correct number of total rows.'));
  261. }
  262. }