The clean & modern RSS server that doesn't give you any crap. https://thearsse.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BaseDriver.php 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. <?php
  2. /** @license MIT
  3. * Copyright 2017 J. King, Dustin Wilson et al.
  4. * See LICENSE and AUTHORS files for details */
  5. declare(strict_types=1);
  6. namespace JKingWeb\Arsse\TestCase\Db;
  7. use JKingWeb\Arsse\Db\Statement;
  8. use JKingWeb\Arsse\Db\Result;
  9. use JKingWeb\Arsse\Test\DatabaseInformation;
  10. abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
  11. protected static $dbInfo;
  12. protected static $interface;
  13. protected $drv;
  14. protected $create;
  15. protected $lock;
  16. protected $setVersion;
  17. protected static $conf = [
  18. 'dbTimeoutExec' => 0.5,
  19. 'dbSQLite3Timeout' => 0,
  20. //'dbSQLite3File' => "(temporary file)",
  21. ];
  22. public static function setUpBeforeClass() {
  23. // establish a clean baseline
  24. static::clearData();
  25. static::$dbInfo = new DatabaseInformation(static::$implementation);
  26. static::setConf(static::$conf);
  27. static::$interface = (static::$dbInfo->interfaceConstructor)();
  28. }
  29. public function setUp() {
  30. self::clearData();
  31. self::setConf(static::$conf);
  32. if (!static::$interface) {
  33. $this->markTestSkipped(static::$implementation." database driver not available");
  34. }
  35. // completely clear the database and ensure the schema version can easily be altered
  36. (static::$dbInfo->razeFunction)(static::$interface, [
  37. "CREATE TABLE arsse_meta(key varchar(255) primary key not null, value text)",
  38. "INSERT INTO arsse_meta(key,value) values('schema_version','0')",
  39. ]);
  40. // construct a fresh driver for each test
  41. $this->drv = new static::$dbInfo->driverClass;
  42. }
  43. public function tearDown() {
  44. // deconstruct the driver
  45. unset($this->drv);
  46. if (static::$interface) {
  47. // completely clear the database
  48. (static::$dbInfo->razeFunction)(static::$interface);
  49. }
  50. self::clearData();
  51. }
  52. public static function tearDownAfterClass() {
  53. static::$implementation = null;
  54. static::$dbInfo = null;
  55. self::clearData();
  56. }
  57. protected function exec($q): bool {
  58. // PDO implementation
  59. $q = (!is_array($q)) ? [$q] : $q;
  60. foreach ($q as $query) {
  61. static::$interface->exec((string) $query);
  62. }
  63. return true;
  64. }
  65. protected function query(string $q) {
  66. // PDO implementation
  67. return static::$interface->query($q)->fetchColumn();
  68. }
  69. # TESTS
  70. public function testFetchDriverName() {
  71. $class = get_class($this->drv);
  72. $this->assertTrue(strlen($class::driverName()) > 0);
  73. }
  74. public function testFetchSchemaId() {
  75. $class = get_class($this->drv);
  76. $this->assertTrue(strlen($class::schemaID()) > 0);
  77. }
  78. public function testCheckCharacterSetAcceptability() {
  79. $this->assertTrue($this->drv->charsetAcceptable());
  80. }
  81. public function testTranslateAToken() {
  82. $this->assertRegExp("/^[a-z][a-z0-9]*$/i", $this->drv->sqlToken("greatest"));
  83. $this->assertSame("distinct", $this->drv->sqlToken("distinct"));
  84. }
  85. public function testExecAValidStatement() {
  86. $this->assertTrue($this->drv->exec($this->create));
  87. }
  88. public function testExecAnInvalidStatement() {
  89. $this->assertException("engineErrorGeneral", "Db");
  90. $this->drv->exec("And the meek shall inherit the earth...");
  91. }
  92. public function testExecMultipleStatements() {
  93. $this->assertTrue($this->drv->exec("$this->create; INSERT INTO arsse_test(id) values(2112)"));
  94. $this->assertEquals(2112, $this->query("SELECT id from arsse_test"));
  95. }
  96. public function testExecTimeout() {
  97. $this->exec($this->create);
  98. $this->exec($this->lock);
  99. $this->assertException("general", "Db", "ExceptionTimeout");
  100. $lock = is_array($this->lock) ? implode("; ", $this->lock) : $this->lock;
  101. $this->drv->exec($lock);
  102. }
  103. public function testExecConstraintViolation() {
  104. $this->drv->exec("CREATE TABLE arsse_test(id varchar(255) not null)");
  105. $this->assertException("constraintViolation", "Db", "ExceptionInput");
  106. $this->drv->exec("INSERT INTO arsse_test default values");
  107. }
  108. public function testExecTypeViolation() {
  109. $this->drv->exec($this->create);
  110. $this->assertException("typeViolation", "Db", "ExceptionInput");
  111. $this->drv->exec("INSERT INTO arsse_test(id) values('ook')");
  112. }
  113. public function testMakeAValidQuery() {
  114. $this->assertInstanceOf(Result::class, $this->drv->query("SELECT 1"));
  115. }
  116. public function testMakeAnInvalidQuery() {
  117. $this->assertException("engineErrorGeneral", "Db");
  118. $this->drv->query("Apollo was astonished; Dionysus thought me mad");
  119. }
  120. public function testQueryTimeout() {
  121. $this->exec($this->create);
  122. $this->exec($this->lock);
  123. $this->assertException("general", "Db", "ExceptionTimeout");
  124. $lock = is_array($this->lock) ? implode("; ", $this->lock) : $this->lock;
  125. $this->drv->exec($lock);
  126. }
  127. public function testQueryConstraintViolation() {
  128. $this->drv->exec("CREATE TABLE arsse_test(id integer not null)");
  129. $this->assertException("constraintViolation", "Db", "ExceptionInput");
  130. $this->drv->query("INSERT INTO arsse_test default values");
  131. }
  132. public function testQueryTypeViolation() {
  133. $this->drv->exec($this->create);
  134. $this->assertException("typeViolation", "Db", "ExceptionInput");
  135. $this->drv->query("INSERT INTO arsse_test(id) values('ook')");
  136. }
  137. public function testPrepareAValidQuery() {
  138. $s = $this->drv->prepare("SELECT ?, ?", "int", "int");
  139. $this->assertInstanceOf(Statement::class, $s);
  140. }
  141. public function testPrepareAnInvalidQuery() {
  142. $this->assertException("engineErrorGeneral", "Db");
  143. $s = $this->drv->prepare("This is an invalid query", "int", "int")->run();
  144. }
  145. public function testCreateASavepoint() {
  146. $this->assertEquals(1, $this->drv->savepointCreate());
  147. $this->assertEquals(2, $this->drv->savepointCreate());
  148. $this->assertEquals(3, $this->drv->savepointCreate());
  149. }
  150. public function testReleaseASavepoint() {
  151. $this->assertEquals(1, $this->drv->savepointCreate());
  152. $this->assertEquals(true, $this->drv->savepointRelease());
  153. $this->assertException("savepointInvalid", "Db");
  154. $this->drv->savepointRelease();
  155. }
  156. public function testUndoASavepoint() {
  157. $this->assertEquals(1, $this->drv->savepointCreate());
  158. $this->assertEquals(true, $this->drv->savepointUndo());
  159. $this->assertException("savepointInvalid", "Db");
  160. $this->drv->savepointUndo();
  161. }
  162. public function testManipulateSavepoints() {
  163. $this->assertEquals(1, $this->drv->savepointCreate());
  164. $this->assertEquals(2, $this->drv->savepointCreate());
  165. $this->assertEquals(3, $this->drv->savepointCreate());
  166. $this->assertEquals(4, $this->drv->savepointCreate());
  167. $this->assertEquals(5, $this->drv->savepointCreate());
  168. $this->assertTrue($this->drv->savepointUndo(3));
  169. $this->assertFalse($this->drv->savepointRelease(4));
  170. $this->assertEquals(6, $this->drv->savepointCreate());
  171. $this->assertFalse($this->drv->savepointRelease(5));
  172. $this->assertTrue($this->drv->savepointRelease(6));
  173. $this->assertEquals(3, $this->drv->savepointCreate());
  174. $this->assertTrue($this->drv->savepointRelease(2));
  175. $this->assertException("savepointStale", "Db");
  176. $this->drv->savepointRelease(2);
  177. }
  178. public function testManipulateSavepointsSomeMore() {
  179. $this->assertEquals(1, $this->drv->savepointCreate());
  180. $this->assertEquals(2, $this->drv->savepointCreate());
  181. $this->assertEquals(3, $this->drv->savepointCreate());
  182. $this->assertEquals(4, $this->drv->savepointCreate());
  183. $this->assertTrue($this->drv->savepointRelease(2));
  184. $this->assertFalse($this->drv->savepointUndo(3));
  185. $this->assertException("savepointStale", "Db");
  186. $this->drv->savepointUndo(2);
  187. }
  188. public function testBeginATransaction() {
  189. $select = "SELECT count(*) FROM arsse_test";
  190. $insert = "INSERT INTO arsse_test default values";
  191. $this->drv->exec($this->create);
  192. $tr = $this->drv->begin();
  193. $this->drv->query($insert);
  194. $this->assertEquals(1, $this->drv->query($select)->getValue());
  195. $this->assertEquals(0, $this->query($select));
  196. $this->drv->query($insert);
  197. $this->assertEquals(2, $this->drv->query($select)->getValue());
  198. $this->assertEquals(0, $this->query($select));
  199. }
  200. public function testCommitATransaction() {
  201. $select = "SELECT count(*) FROM arsse_test";
  202. $insert = "INSERT INTO arsse_test default values";
  203. $this->drv->exec($this->create);
  204. $tr = $this->drv->begin();
  205. $this->drv->query($insert);
  206. $this->assertEquals(1, $this->drv->query($select)->getValue());
  207. $this->assertEquals(0, $this->query($select));
  208. $tr->commit();
  209. $this->assertEquals(1, $this->drv->query($select)->getValue());
  210. $this->assertEquals(1, $this->query($select));
  211. }
  212. public function testRollbackATransaction() {
  213. $select = "SELECT count(*) FROM arsse_test";
  214. $insert = "INSERT INTO arsse_test default values";
  215. $this->drv->exec($this->create);
  216. $tr = $this->drv->begin();
  217. $this->drv->query($insert);
  218. $this->assertEquals(1, $this->drv->query($select)->getValue());
  219. $this->assertEquals(0, $this->query($select));
  220. $tr->rollback();
  221. $this->assertEquals(0, $this->drv->query($select)->getValue());
  222. $this->assertEquals(0, $this->query($select));
  223. }
  224. public function testBeginChainedTransactions() {
  225. $select = "SELECT count(*) FROM arsse_test";
  226. $insert = "INSERT INTO arsse_test default values";
  227. $this->drv->exec($this->create);
  228. $tr1 = $this->drv->begin();
  229. $this->drv->query($insert);
  230. $this->assertEquals(1, $this->drv->query($select)->getValue());
  231. $this->assertEquals(0, $this->query($select));
  232. $tr2 = $this->drv->begin();
  233. $this->drv->query($insert);
  234. $this->assertEquals(2, $this->drv->query($select)->getValue());
  235. $this->assertEquals(0, $this->query($select));
  236. }
  237. public function testCommitChainedTransactions() {
  238. $select = "SELECT count(*) FROM arsse_test";
  239. $insert = "INSERT INTO arsse_test default values";
  240. $this->drv->exec($this->create);
  241. $tr1 = $this->drv->begin();
  242. $this->drv->query($insert);
  243. $this->assertEquals(1, $this->drv->query($select)->getValue());
  244. $this->assertEquals(0, $this->query($select));
  245. $tr2 = $this->drv->begin();
  246. $this->drv->query($insert);
  247. $this->assertEquals(2, $this->drv->query($select)->getValue());
  248. $this->assertEquals(0, $this->query($select));
  249. $tr2->commit();
  250. $this->assertEquals(0, $this->query($select));
  251. $tr1->commit();
  252. $this->assertEquals(2, $this->query($select));
  253. }
  254. public function testCommitChainedTransactionsOutOfOrder() {
  255. $select = "SELECT count(*) FROM arsse_test";
  256. $insert = "INSERT INTO arsse_test default values";
  257. $this->drv->exec($this->create);
  258. $tr1 = $this->drv->begin();
  259. $this->drv->query($insert);
  260. $this->assertEquals(1, $this->drv->query($select)->getValue());
  261. $this->assertEquals(0, $this->query($select));
  262. $tr2 = $this->drv->begin();
  263. $this->drv->query($insert);
  264. $this->assertEquals(2, $this->drv->query($select)->getValue());
  265. $this->assertEquals(0, $this->query($select));
  266. $tr1->commit();
  267. $this->assertEquals(2, $this->query($select));
  268. $tr2->commit();
  269. }
  270. public function testRollbackChainedTransactions() {
  271. $select = "SELECT count(*) FROM arsse_test";
  272. $insert = "INSERT INTO arsse_test default values";
  273. $this->drv->exec($this->create);
  274. $tr1 = $this->drv->begin();
  275. $this->drv->query($insert);
  276. $this->assertEquals(1, $this->drv->query($select)->getValue());
  277. $this->assertEquals(0, $this->query($select));
  278. $tr2 = $this->drv->begin();
  279. $this->drv->query($insert);
  280. $this->assertEquals(2, $this->drv->query($select)->getValue());
  281. $this->assertEquals(0, $this->query($select));
  282. $tr2->rollback();
  283. $this->assertEquals(1, $this->drv->query($select)->getValue());
  284. $this->assertEquals(0, $this->query($select));
  285. $tr1->rollback();
  286. $this->assertEquals(0, $this->drv->query($select)->getValue());
  287. $this->assertEquals(0, $this->query($select));
  288. }
  289. public function testRollbackChainedTransactionsOutOfOrder() {
  290. $select = "SELECT count(*) FROM arsse_test";
  291. $insert = "INSERT INTO arsse_test default values";
  292. $this->drv->exec($this->create);
  293. $tr1 = $this->drv->begin();
  294. $this->drv->query($insert);
  295. $this->assertEquals(1, $this->drv->query($select)->getValue());
  296. $this->assertEquals(0, $this->query($select));
  297. $tr2 = $this->drv->begin();
  298. $this->drv->query($insert);
  299. $this->assertEquals(2, $this->drv->query($select)->getValue());
  300. $this->assertEquals(0, $this->query($select));
  301. $tr1->rollback();
  302. $this->assertEquals(0, $this->drv->query($select)->getValue());
  303. $this->assertEquals(0, $this->query($select));
  304. $tr2->rollback();
  305. $this->assertEquals(0, $this->drv->query($select)->getValue());
  306. $this->assertEquals(0, $this->query($select));
  307. }
  308. public function testPartiallyRollbackChainedTransactions() {
  309. $select = "SELECT count(*) FROM arsse_test";
  310. $insert = "INSERT INTO arsse_test default values";
  311. $this->drv->exec($this->create);
  312. $tr1 = $this->drv->begin();
  313. $this->drv->query($insert);
  314. $this->assertEquals(1, $this->drv->query($select)->getValue());
  315. $this->assertEquals(0, $this->query($select));
  316. $tr2 = $this->drv->begin();
  317. $this->drv->query($insert);
  318. $this->assertEquals(2, $this->drv->query($select)->getValue());
  319. $this->assertEquals(0, $this->query($select));
  320. $tr2->rollback();
  321. $this->assertEquals(1, $this->drv->query($select)->getValue());
  322. $this->assertEquals(0, $this->query($select));
  323. $tr1->commit();
  324. $this->assertEquals(1, $this->drv->query($select)->getValue());
  325. $this->assertEquals(1, $this->query($select));
  326. }
  327. public function testFetchSchemaVersion() {
  328. $this->assertSame(0, $this->drv->schemaVersion());
  329. $this->drv->exec(str_replace("#", "1", $this->setVersion));
  330. $this->assertSame(1, $this->drv->schemaVersion());
  331. $this->drv->exec(str_replace("#", "2", $this->setVersion));
  332. $this->assertSame(2, $this->drv->schemaVersion());
  333. // SQLite is unaffected by the removal of the metadata table; other backends are
  334. // in neither case should a query for the schema version produce an error, however
  335. $this->exec("DROP TABLE IF EXISTS arsse_meta");
  336. $exp = (static::$dbInfo->backend == "SQLite 3") ? 2 : 0;
  337. $this->assertSame($exp, $this->drv->schemaVersion());
  338. }
  339. public function testLockTheDatabase() {
  340. // PostgreSQL doesn't actually lock the whole database, only the metadata table
  341. // normally the application will first query this table to ensure the schema version is correct,
  342. // so the effect is usually the same
  343. $this->drv->savepointCreate(true);
  344. $this->assertException();
  345. $this->exec($this->lock);
  346. }
  347. public function testUnlockTheDatabase() {
  348. $this->drv->savepointCreate(true);
  349. $this->drv->savepointRelease();
  350. $this->drv->savepointCreate(true);
  351. $this->drv->savepointUndo();
  352. $this->assertTrue($this->exec(str_replace("#", "3", $this->setVersion)));
  353. }
  354. }