From 9052457fb00e6d9304d2abfaa78361c33db5550a Mon Sep 17 00:00:00 2001 From: haszi Date: Thu, 28 Mar 2024 17:00:58 +0100 Subject: [PATCH] Inject database dependency into constructor of Index Add new IndexRepository class to handle all database access. Inject IndexRepository as a dependency into the constructor of Index and use that instead of directly accessing the database. Add basic indexing test. Fix existing test that uses indexing to use IndexRepository. --- phpdotnet/phd/Index.php | 151 ++++++++---------------------- phpdotnet/phd/IndexRepository.php | 119 +++++++++++++++++++++++ render.php | 12 ++- tests/index/bug_GH-98.phpt | 10 +- tests/index/data/indexing_001.xml | 120 ++++++++++++++++++++++++ tests/index/indexing_001.phpt | 68 ++++++++++++++ 6 files changed, 368 insertions(+), 112 deletions(-) create mode 100644 phpdotnet/phd/IndexRepository.php create mode 100644 tests/index/data/indexing_001.xml create mode 100644 tests/index/indexing_001.phpt diff --git a/phpdotnet/phd/Index.php b/phpdotnet/phd/Index.php index 7853b9dc..f0e9cf15 100644 --- a/phpdotnet/phd/Index.php +++ b/phpdotnet/phd/Index.php @@ -83,7 +83,7 @@ class Index extends Format 'phpdoc' => 'PI_PHPDOCHandler', ); - private $db; + private IndexRepository $indexRepository; private $currentchunk; private $ids = array(); private $currentid; @@ -91,7 +91,6 @@ class Index extends Format private $isChunk = array(); protected $nfo = array(); private $isSectionChunk = array(); - private $log = ''; private $previousId = ""; private $inChangelog = false; private $currentChangelog = array(); @@ -101,6 +100,11 @@ class Index extends Format private $POST_REPLACEMENT_INDEXES = array(); private $POST_REPLACEMENT_VALUES = array(); + public function __construct(IndexRepository $indexRepository) { + $this->indexRepository = $indexRepository; + parent::__construct(); + } + public function transformFromMap($open, $tag, $name, $attrs, $props) { } public function TEXT($value) { @@ -128,58 +132,24 @@ public function update($event, $value = null) break; case Render::INIT: if ($value) { - if (Config::memoryindex()) { - $db = new \SQLite3(":memory:"); - } else { - $db = new \SQLite3(Config::output_dir() . 'index.sqlite'); - $db->exec('DROP TABLE IF EXISTS ids'); - $db->exec('DROP TABLE IF EXISTS indexing'); - $db->exec('DROP TABLE IF EXISTS changelogs'); - } - - $create = <<exec('PRAGMA default_synchronous=OFF'); - $db->exec('PRAGMA count_changes=OFF'); - $db->exec('PRAGMA cache_size=100000'); - $db->exec($create); - - if (Config::memoryindex()) { - Config::set_indexcache($db); - } - - $this->db = $db; + $this->indexRepository->init(); $this->chunks = array(); } else { print_r($this->chunks); } break; case Render::FINALIZE: - $retval = $this->db->exec("BEGIN TRANSACTION; INSERT INTO indexing (time) VALUES ('" . time() . "'); COMMIT"); - $this->commit(); - if ($this->db->lastErrorCode()) { - trigger_error($this->db->lastErrorMsg(), E_USER_WARNING); + $this->indexRepository->saveIndexingTime(time()); + if ($this->indexRepository->commit( + $this->commit, + $this->POST_REPLACEMENT_INDEXES, + $this->POST_REPLACEMENT_VALUES, + $this->changelog, + )) { + $this->commit = []; + } + if ($this->indexRepository->lastErrorCode()) { + trigger_error($this->indexRepository->lastErrorMsg(), E_USER_WARNING); } break; } @@ -222,7 +192,7 @@ protected function storeInfo($elm, $id, $filename, $isChunk = true) { ); // Append "next" to the previously inserted row if ($isChunk) { - $this->POST_REPLACEMENT_VALUES[$this->previousId] = $this->db->escapeString($id); + $this->POST_REPLACEMENT_VALUES[$this->previousId] = $id; $this->previousId = $id; } } @@ -241,34 +211,33 @@ public function appendID() { $sdesc = $lastChunk["sdesc"]; } - $this->commit[++$idx] = sprintf( - "INSERT INTO ids (docbook_id, filename, parent_id, sdesc, ldesc, element, previous, next, chunk) VALUES('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d);\n", - $this->db->escapeString($lastChunkId), - $this->db->escapeString($lastChunk["filename"]), - $this->db->escapeString($this->currentchunk), - $this->db->escapeString($sdesc), - $this->db->escapeString($lastChunk["ldesc"]), - $this->db->escapeString($lastChunk["element"]), - $this->db->escapeString($lastChunk["previous"]), - $this->db->escapeString($lastChunk["chunk"] ? "POST-REPLACEMENT" : ""), - $this->db->escapeString($lastChunk["chunk"]) - ); + $this->commit[++$idx] = [ + "docbook_id" => $lastChunkId, + "filename" => $lastChunk["filename"], + "parent_id" => $this->currentchunk, + "sdesc" => $sdesc, + "ldesc" => $lastChunk["ldesc"], + "element" => $lastChunk["element"], + "previous" => $lastChunk["previous"], + "next" => ($lastChunk["chunk"] ? "POST-REPLACEMENT" : ""), + "chunk" => $lastChunk["chunk"], + ]; if ($lastChunk["chunk"]) { $this->POST_REPLACEMENT_INDEXES[] = array("docbook_id" => $lastChunkId, "idx" => $idx); } if ($array === true) { foreach($lastChunk["sdesc"] as $sdesc) { - $this->commit[++$idx] = sprintf( - "INSERT INTO ids (docbook_id, filename, parent_id, sdesc, ldesc, element, previous, next, chunk) VALUES('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', 0);\n", - $this->db->escapeString($lastChunkId), - $this->db->escapeString($lastChunk["filename"]), - $this->db->escapeString($this->currentchunk), - $this->db->escapeString($sdesc), - $this->db->escapeString($lastChunk["ldesc"]), - $this->db->escapeString($lastChunk["element"]), - $this->db->escapeString($lastChunk["previous"]), - $this->db->escapeString($lastChunk["chunk"] ? "POST-REPLACEMENT" : "") - ); + $this->commit[++$idx] = [ + "docbook_id" => $lastChunkId, + "filename" => $lastChunk["filename"], + "parent_id" => $this->currentchunk, + "sdesc" => $sdesc, + "ldesc" => $lastChunk["ldesc"], + "element" => $lastChunk["element"], + "previous" => $lastChunk["previous"], + "next" => ($lastChunk["chunk"] ? "POST-REPLACEMENT" : ""), + "chunk" => 0, + ]; $this->POST_REPLACEMENT_INDEXES[] = array("docbook_id" => $lastChunkId, "idx" => $idx); } } @@ -431,44 +400,6 @@ public function format_row($open, $name, $attrs, $props) { } } - - - public function commit() { - if (isset($this->commit) && $this->commit) { - $search = $this->db->escapeString("POST-REPLACEMENT"); - $none = $this->db->escapeString(""); - - foreach($this->POST_REPLACEMENT_INDEXES as $a) { - if (isset($this->POST_REPLACEMENT_VALUES[$a["docbook_id"]])) { - $replacement = $this->POST_REPLACEMENT_VALUES[$a["docbook_id"]]; - $this->commit[$a["idx"]] = str_replace($search, $replacement, $this->commit[$a["idx"]]); - } else { - // If there are still post replacement, then they don't have - // any 'next' page - $this->commit[$a["idx"]] = str_replace($search, $none, $this->commit[$a["idx"]]); - } - } - - $this->db->exec('BEGIN TRANSACTION; '.implode("", $this->commit).' COMMIT'); - $log = ""; - foreach($this->changelog as $id => $arr) { - foreach($arr as $entry) { - $log .= sprintf( - "INSERT INTO changelogs (membership, docbook_id, parent_id, version, description) VALUES('%s', '%s', '%s', '%s', '%s');\n", - $this->db->escapeString($entry[0] ?? ''), - $this->db->escapeString($id), - $this->db->escapeString($entry[1]), - $this->db->escapeString($entry[2]), - $this->db->escapeString($entry[3]) - ); - } - } - $this->db->exec('BEGIN TRANSACTION; ' . $log. ' COMMIT'); - $this->log = ""; - $this->commit = array(); - } - } - public function processFilename() { static $dbhtml = null; if ($dbhtml == null) { diff --git a/phpdotnet/phd/IndexRepository.php b/phpdotnet/phd/IndexRepository.php new file mode 100644 index 00000000..24758006 --- /dev/null +++ b/phpdotnet/phd/IndexRepository.php @@ -0,0 +1,119 @@ +db->exec('DROP TABLE IF EXISTS ids'); + $this->db->exec('DROP TABLE IF EXISTS indexing'); + $this->db->exec('DROP TABLE IF EXISTS changelogs'); + $this->db->exec('PRAGMA default_synchronous=OFF'); + $this->db->exec('PRAGMA count_changes=OFF'); + $this->db->exec('PRAGMA cache_size=100000'); + $this->db->exec($create); + } + + public function saveIndexingTime(int $time): void { + $this->db->exec("BEGIN TRANSACTION; INSERT INTO indexing (time) VALUES ('" . $time . "'); COMMIT"); + } + + public function commit( + array $commitList, + array $postReplacementIndexes, + array $postReplacementValues, + array $changelog, + ): bool { + if (!$commitList) { + return false; + } + + foreach ($postReplacementValues as $key => $postReplacementValue) { + $postReplacementValues[$key] = $this->db->escapeString($postReplacementValue); + } + + foreach ($commitList as $key => $commit) { + $commitList[$key] = sprintf( + "INSERT INTO ids (docbook_id, filename, parent_id, sdesc, ldesc, element, previous, next, chunk) VALUES('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d);\n", + $this->db->escapeString($commit["docbook_id"]), + $this->db->escapeString($commit["filename"]), + $this->db->escapeString($commit["parent_id"]), + $this->db->escapeString($commit["sdesc"]), + $this->db->escapeString($commit["ldesc"]), + $this->db->escapeString($commit["element"]), + $this->db->escapeString($commit["previous"]), + $this->db->escapeString($commit["next"]), + $this->db->escapeString($commit["chunk"]) + ); + } + + $search = $this->db->escapeString("POST-REPLACEMENT"); + $none = $this->db->escapeString(""); + + foreach($postReplacementIndexes as $a) { + if (isset($postReplacementValues[$a["docbook_id"]])) { + $replacement = $postReplacementValues[$a["docbook_id"]]; + $commitList[$a["idx"]] = str_replace($search, $replacement, $commitList[$a["idx"]]); + } else { + // If there are still post replacement, then they don't have + // any 'next' page + $commitList[$a["idx"]] = str_replace($search, $none, $commitList[$a["idx"]]); + } + } + + $this->db->exec('BEGIN TRANSACTION; '.implode("", $commitList).' COMMIT'); + $this->saveChangelogs($changelog); + return true; + } + + private function saveChangelogs(array $changelog): void { + $log = ""; + foreach($changelog as $id => $arr) { + foreach($arr as $entry) { + $log .= sprintf( + "INSERT INTO changelogs (membership, docbook_id, parent_id, version, description) VALUES('%s', '%s', '%s', '%s', '%s');\n", + $this->db->escapeString($entry[0] ?? ''), + $this->db->escapeString($id), + $this->db->escapeString($entry[1]), + $this->db->escapeString($entry[2]), + $this->db->escapeString($entry[3]) + ); + } + } + $this->db->exec('BEGIN TRANSACTION; ' . $log. ' COMMIT'); + } + + public function lastErrorCode(): int { + return $this->db->lastErrorCode(); + } + + public function lastErrorMsg(): string { + return $this->db->lastErrorMsg(); + } +} diff --git a/render.php b/render.php index dc390baf..52c80303 100644 --- a/render.php +++ b/render.php @@ -104,13 +104,23 @@ function make_reader() { // Indexing if (requireIndexing(new Config, $db)) { v("Indexing...", VERBOSE_INDEXING); + if (Config::memoryindex()) { + $db = new \SQLite3(":memory:"); + } else { + $db = $db ?? new \SQLite3(Config::output_dir() . 'index.sqlite'); + } // Create indexer - $format = new Index; + $indexRepository = new IndexRepository($db); + $format = new Index($indexRepository); $render->attach($format); $reader->open(Config::xml_file(), NULL, $readerOpts); $render->execute($reader); + if (Config::memoryindex()) { + Config::set_indexcache($db); + } + $render->detach($format); v("Indexing done", VERBOSE_INDEXING); diff --git a/tests/index/bug_GH-98.phpt b/tests/index/bug_GH-98.phpt index dcc9e434..eef10c01 100644 --- a/tests/index/bug_GH-98.phpt +++ b/tests/index/bug_GH-98.phpt @@ -13,7 +13,13 @@ Config::init([ "xml_file" => $xml_file, ]); -$index = new TestIndex; +$indexRepository = new IndexRepository(new \SQLite3( + Config::output_dir() . 'index.sqlite', + \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE +)); +$indexRepository->init(); + +$index = new TestIndex($indexRepository); $render = new TestRender(new Reader, new Config, null, $index); $render->run(); @@ -27,6 +33,8 @@ var_dump(in_array("another.non-chunked.element.id", $indexes)); var_dump(in_array("chunked.element.id", $indexes)); var_dump(in_array("another.chunked.element.id", $indexes)); var_dump(in_array("bug-GH-98", $indexes)); + +unlink(Config::output_dir() . 'index.sqlite'); ?> --EXPECT-- Indexes stored: diff --git a/tests/index/data/indexing_001.xml b/tests/index/data/indexing_001.xml new file mode 100644 index 00000000..2559d690 --- /dev/null +++ b/tests/index/data/indexing_001.xml @@ -0,0 +1,120 @@ + + + + PHP Manual + + + + + + AB + + + + + + 1997- + the PHP Documentation Group + + + + Copyright + + If you are interested in redistribution or republishing of this document + in whole or in part, either modified or unmodified, and you have questions, + please contact the Copyright holders at + doc-license@lists.php.net. + Note that this address is mapped to a publicly archived mailing list. + + + + + + PHP Manual + + + + Preface + + + PHP, which stands for "PHP: Hypertext + Preprocessor" is a widely-used Open Source general-purpose + scripting language + + + + +
+ Authors and Contributors + +
+ Authors and Editors + + The following contributors should be + recognized for the impact they have made and/or continue to make by adding + content to the manual: + +
+
+
+
+ + + Installing/Configuring +
+ Requirements + + As of version 1.16.0, the driver requires PHP 7.2 or higher. Previous + versions of the driver allow compatibility with older PHP versions. + +
+
+ + + + Introduction + +
+ What is PHP? + + PHP (recursive acronym for PHP: Hypertext + Preprocessor) is a widely-used open source general-purpose + scripting language that is especially suited for web + development and can be embedded into HTML. + +
+
+
+ + + Predefined Constants + + The constants below are defined by this extension, and + will only be available when the extension has either + been compiled into PHP or dynamically loaded at runtime. + + + + + Predefined Interfaces and Classes + + + See also the SPL Interfaces and reserved classes. + + + + + + The <interfacename>Traversable</interfacename> interface + Traversable + +
+ Changelog + + Changes + +
+
+
+ +
diff --git a/tests/index/indexing_001.phpt b/tests/index/indexing_001.phpt new file mode 100644 index 00000000..498e0f85 --- /dev/null +++ b/tests/index/indexing_001.phpt @@ -0,0 +1,68 @@ +--TEST-- +Indexing 001 - Basic indexing +--FILE-- + true, + "xml_file" => $xml_file, +]); + +$indexRepository = new IndexRepository(new \SQLite3( + Config::output_dir() . 'index.sqlite', + \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE +)); +$indexRepository->init(); + +$index = new TestIndex($indexRepository); +$render = new TestRender(new Reader, new Config, null, $index); + +$render->run(); + +$indexes = array_keys($index->getNfo()); + +echo "Indexes stored:\n"; + +var_dump($indexes); + +unlink(Config::output_dir() . 'index.sqlite'); +?> +--EXPECT-- +Indexes stored: +array(15) { + [0]=> + string(5) "index" + [1]=> + string(8) "bookinfo" + [2]=> + string(7) "authors" + [3]=> + string(9) "copyright" + [4]=> + string(6) "manual" + [5]=> + string(7) "preface" + [6]=> + string(12) "contributors" + [7]=> + string(13) "mongodb.setup" + [8]=> + string(20) "mongodb.requirements" + [9]=> + string(13) "chapterInBook" + [10]=> + string(12) "introduction" + [11]=> + string(12) "intro-whatis" + [12]=> + string(14) "apcu.constants" + [13]=> + string(19) "reserved.interfaces" + [14]=> + string(17) "class.traversable" +}