ConnectionTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <?php
  2. /**
  3. * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
  4. * This file is licensed under the Affero General Public License version 3 or
  5. * later.
  6. * See the COPYING-README file.
  7. */
  8. namespace Test\DB;
  9. use Doctrine\DBAL\Platforms\SqlitePlatform;
  10. use OC\DB\MDB2SchemaManager;
  11. use OCP\DB\QueryBuilder\IQueryBuilder;
  12. /**
  13. * Class Connection
  14. *
  15. * @group DB
  16. *
  17. * @package Test\DB
  18. */
  19. class ConnectionTest extends \Test\TestCase {
  20. /**
  21. * @var \OCP\IDBConnection
  22. */
  23. private $connection;
  24. public static function setUpBeforeClass(): void {
  25. self::dropTestTable();
  26. parent::setUpBeforeClass();
  27. }
  28. public static function tearDownAfterClass(): void {
  29. self::dropTestTable();
  30. parent::tearDownAfterClass();
  31. }
  32. protected static function dropTestTable() {
  33. if (\OC::$server->getConfig()->getSystemValue('dbtype', 'sqlite') !== 'oci') {
  34. \OC::$server->getDatabaseConnection()->dropTable('table');
  35. }
  36. }
  37. protected function setUp(): void {
  38. parent::setUp();
  39. $this->connection = \OC::$server->getDatabaseConnection();
  40. }
  41. protected function tearDown(): void {
  42. parent::tearDown();
  43. $this->connection->dropTable('table');
  44. }
  45. /**
  46. * @param string $table
  47. */
  48. public function assertTableExist($table) {
  49. if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
  50. // sqlite removes the tables after closing the DB
  51. $this->addToAssertionCount(1);
  52. } else {
  53. $this->assertTrue($this->connection->tableExists($table), 'Table ' . $table . ' exists.');
  54. }
  55. }
  56. /**
  57. * @param string $table
  58. */
  59. public function assertTableNotExist($table) {
  60. if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
  61. // sqlite removes the tables after closing the DB
  62. $this->addToAssertionCount(1);
  63. } else {
  64. $this->assertFalse($this->connection->tableExists($table), 'Table ' . $table . " doesn't exist.");
  65. }
  66. }
  67. private function makeTestTable() {
  68. $schemaManager = new MDB2SchemaManager($this->connection);
  69. $schemaManager->createDbFromStructure(__DIR__ . '/testschema.xml');
  70. }
  71. public function testTableExists() {
  72. $this->assertTableNotExist('table');
  73. $this->makeTestTable();
  74. $this->assertTableExist('table');
  75. }
  76. /**
  77. * @depends testTableExists
  78. */
  79. public function testDropTable() {
  80. $this->makeTestTable();
  81. $this->assertTableExist('table');
  82. $this->connection->dropTable('table');
  83. $this->assertTableNotExist('table');
  84. }
  85. private function getTextValueByIntegerField($integerField) {
  86. $builder = $this->connection->getQueryBuilder();
  87. $query = $builder->select('*')
  88. ->from('table')
  89. ->where($builder->expr()->eq('integerfield', $builder->createNamedParameter($integerField, IQueryBuilder::PARAM_INT)));
  90. $result = $query->execute();
  91. $row = $result->fetch();
  92. $result->closeCursor();
  93. return $row['textfield'] ?? null;
  94. }
  95. public function testSetValues() {
  96. $this->makeTestTable();
  97. $this->connection->setValues('table', [
  98. 'integerfield' => 1
  99. ], [
  100. 'textfield' => 'foo',
  101. 'clobfield' => 'not_null'
  102. ]);
  103. $this->assertEquals('foo', $this->getTextValueByIntegerField(1));
  104. }
  105. public function testSetValuesOverWrite() {
  106. $this->makeTestTable();
  107. $this->connection->setValues('table', [
  108. 'integerfield' => 1
  109. ], [
  110. 'textfield' => 'foo'
  111. ]);
  112. $this->connection->setValues('table', [
  113. 'integerfield' => 1
  114. ], [
  115. 'textfield' => 'bar'
  116. ]);
  117. $this->assertEquals('bar', $this->getTextValueByIntegerField(1));
  118. }
  119. public function testSetValuesOverWritePrecondition() {
  120. $this->makeTestTable();
  121. $this->connection->setValues('table', [
  122. 'integerfield' => 1
  123. ], [
  124. 'textfield' => 'foo',
  125. 'booleanfield' => true,
  126. 'clobfield' => 'not_null'
  127. ]);
  128. $this->connection->setValues('table', [
  129. 'integerfield' => 1
  130. ], [
  131. 'textfield' => 'bar'
  132. ], [
  133. 'booleanfield' => true
  134. ]);
  135. $this->assertEquals('bar', $this->getTextValueByIntegerField(1));
  136. }
  137. public function testSetValuesOverWritePreconditionFailed() {
  138. $this->expectException(\OCP\PreConditionNotMetException::class);
  139. $this->makeTestTable();
  140. $this->connection->setValues('table', [
  141. 'integerfield' => 1
  142. ], [
  143. 'textfield' => 'foo',
  144. 'booleanfield' => true,
  145. 'clobfield' => 'not_null'
  146. ]);
  147. $this->connection->setValues('table', [
  148. 'integerfield' => 1
  149. ], [
  150. 'textfield' => 'bar'
  151. ], [
  152. 'booleanfield' => false
  153. ]);
  154. }
  155. public function testSetValuesSameNoError() {
  156. $this->makeTestTable();
  157. $this->connection->setValues('table', [
  158. 'integerfield' => 1
  159. ], [
  160. 'textfield' => 'foo',
  161. 'clobfield' => 'not_null'
  162. ]);
  163. // this will result in 'no affected rows' on certain optimizing DBs
  164. // ensure the PreConditionNotMetException isn't thrown
  165. $this->connection->setValues('table', [
  166. 'integerfield' => 1
  167. ], [
  168. 'textfield' => 'foo'
  169. ]);
  170. $this->addToAssertionCount(1);
  171. }
  172. public function testInsertIfNotExist() {
  173. if (\OC::$server->getConfig()->getSystemValue('dbtype', 'sqlite') === 'oci') {
  174. self::markTestSkipped('Insert if not exist does not work with clob on oracle');
  175. }
  176. $this->makeTestTable();
  177. $categoryEntries = [
  178. ['user' => 'test', 'category' => 'Family', 'expectedResult' => 1],
  179. ['user' => 'test', 'category' => 'Friends', 'expectedResult' => 1],
  180. ['user' => 'test', 'category' => 'Coworkers', 'expectedResult' => 1],
  181. ['user' => 'test', 'category' => 'Coworkers', 'expectedResult' => 0],
  182. ['user' => 'test', 'category' => 'School', 'expectedResult' => 1],
  183. ['user' => 'test2', 'category' => 'Coworkers2', 'expectedResult' => 1],
  184. ['user' => 'test2', 'category' => 'Coworkers2', 'expectedResult' => 0],
  185. ['user' => 'test2', 'category' => 'School2', 'expectedResult' => 1],
  186. ['user' => 'test2', 'category' => 'Coworkers', 'expectedResult' => 1],
  187. ];
  188. $row = 0;
  189. foreach ($categoryEntries as $entry) {
  190. $result = $this->connection->insertIfNotExist('*PREFIX*table',
  191. [
  192. 'textfield' => $entry['user'],
  193. 'clobfield' => $entry['category'],
  194. 'integerfield' => $row++,
  195. ], ['textfield', 'clobfield']);
  196. $this->assertEquals($entry['expectedResult'], $result, json_encode($entry));
  197. }
  198. $query = $this->connection->prepare('SELECT * FROM `*PREFIX*table`');
  199. $result = $query->execute();
  200. $this->assertTrue((bool)$result);
  201. $this->assertEquals(7, count($query->fetchAll()));
  202. }
  203. public function testInsertIfNotExistNull() {
  204. if (\OC::$server->getConfig()->getSystemValue('dbtype', 'sqlite') === 'oci') {
  205. self::markTestSkipped('Insert if not exist does not work with clob on oracle');
  206. }
  207. $this->makeTestTable();
  208. $categoryEntries = [
  209. ['addressbookid' => 123, 'fullname' => null, 'expectedResult' => 1],
  210. ['addressbookid' => 123, 'fullname' => null, 'expectedResult' => 0],
  211. ['addressbookid' => 123, 'fullname' => 'test', 'expectedResult' => 1],
  212. ];
  213. $row = 0;
  214. foreach ($categoryEntries as $entry) {
  215. $result = $this->connection->insertIfNotExist('*PREFIX*table',
  216. [
  217. 'integerfield_default' => $entry['addressbookid'],
  218. 'clobfield' => $entry['fullname'],
  219. 'integerfield' => $row++,
  220. ], ['integerfield_default', 'clobfield']);
  221. $this->assertEquals($entry['expectedResult'], $result, json_encode($entry));
  222. }
  223. $query = $this->connection->prepare('SELECT * FROM `*PREFIX*table`');
  224. $result = $query->execute();
  225. $this->assertTrue((bool)$result);
  226. $this->assertEquals(2, count($query->fetchAll()));
  227. }
  228. public function testInsertIfNotExistDonTOverwrite() {
  229. if (\OC::$server->getConfig()->getSystemValue('dbtype', 'sqlite') === 'oci') {
  230. self::markTestSkipped('Insert if not exist does not work with clob on oracle');
  231. }
  232. $this->makeTestTable();
  233. $fullName = 'fullname test';
  234. $uri = 'uri_1';
  235. // Normal test to have same known data inserted.
  236. $query = $this->connection->prepare('INSERT INTO `*PREFIX*table` (`textfield`, `clobfield`) VALUES (?, ?)');
  237. $result = $query->execute([$fullName, $uri]);
  238. $this->assertEquals(1, $result);
  239. $query = $this->connection->prepare('SELECT `textfield`, `clobfield` FROM `*PREFIX*table` WHERE `clobfield` = ?');
  240. $result = $query->execute([$uri]);
  241. $this->assertTrue($result);
  242. $rowset = $query->fetchAll();
  243. $this->assertEquals(1, count($rowset));
  244. $this->assertArrayHasKey('textfield', $rowset[0]);
  245. $this->assertEquals($fullName, $rowset[0]['textfield']);
  246. // Try to insert a new row
  247. $result = $this->connection->insertIfNotExist('*PREFIX*table',
  248. [
  249. 'textfield' => $fullName,
  250. 'clobfield' => $uri,
  251. ]);
  252. $this->assertEquals(0, $result);
  253. $query = $this->connection->prepare('SELECT `textfield`, `clobfield` FROM `*PREFIX*table` WHERE `clobfield` = ?');
  254. $result = $query->execute([$uri]);
  255. $this->assertTrue($result);
  256. // Test that previously inserted data isn't overwritten
  257. // And that a new row hasn't been inserted.
  258. $rowset = $query->fetchAll();
  259. $this->assertEquals(1, count($rowset));
  260. $this->assertArrayHasKey('textfield', $rowset[0]);
  261. $this->assertEquals($fullName, $rowset[0]['textfield']);
  262. }
  263. public function testInsertIfNotExistsViolating() {
  264. if (\OC::$server->getConfig()->getSystemValue('dbtype', 'sqlite') === 'oci') {
  265. self::markTestSkipped('Insert if not exist does not work with clob on oracle');
  266. }
  267. $this->makeTestTable();
  268. $result = $this->connection->insertIfNotExist('*PREFIX*table',
  269. [
  270. 'textfield' => md5('welcome.txt'),
  271. 'clobfield' => $this->getUniqueID()
  272. ]);
  273. $this->assertEquals(1, $result);
  274. $result = $this->connection->insertIfNotExist('*PREFIX*table',
  275. [
  276. 'textfield' => md5('welcome.txt'),
  277. 'clobfield' => $this->getUniqueID()
  278. ],['textfield']);
  279. $this->assertEquals(0, $result);
  280. }
  281. public function insertIfNotExistsViolatingThrows() {
  282. return [
  283. [null],
  284. [['clobfield']],
  285. ];
  286. }
  287. /**
  288. * @dataProvider insertIfNotExistsViolatingThrows
  289. *
  290. * @param array $compareKeys
  291. */
  292. public function testInsertIfNotExistsViolatingUnique($compareKeys) {
  293. if (\OC::$server->getConfig()->getSystemValue('dbtype', 'sqlite') === 'oci') {
  294. self::markTestSkipped('Insert if not exist does not work with clob on oracle');
  295. }
  296. $this->makeTestTable();
  297. $result = $this->connection->insertIfNotExist('*PREFIX*table',
  298. [
  299. 'integerfield' => 1,
  300. 'clobfield' => $this->getUniqueID()
  301. ]);
  302. $this->assertEquals(1, $result);
  303. $result = $this->connection->insertIfNotExist('*PREFIX*table',
  304. [
  305. 'integerfield' => 1,
  306. 'clobfield' => $this->getUniqueID()
  307. ], $compareKeys);
  308. $this->assertEquals(0, $result);
  309. }
  310. public function testUniqueConstraintViolating() {
  311. $this->expectException(\Doctrine\DBAL\Exception\UniqueConstraintViolationException::class);
  312. $this->makeTestTable();
  313. $testQuery = 'INSERT INTO `*PREFIX*table` (`integerfield`, `textfield`) VALUES(?, ?)';
  314. $testParams = [1, 'hello'];
  315. $this->connection->executeUpdate($testQuery, $testParams);
  316. $this->connection->executeUpdate($testQuery, $testParams);
  317. }
  318. }