From 5eb9e8678ddc58a01929a9f340a01048836b47d3 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Sat, 19 Jan 2013 16:59:39 +0100 Subject: Indexer: Added page and meta value rename functions With these functions that search index can be updated after page moves or mass metadata updates without the need to reindex the whole page/wiki. These functions will be used by the new pagemove plugin. --- inc/indexer.php | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/inc/indexer.php b/inc/indexer.php index 7a62345bf..70eac035b 100644 --- a/inc/indexer.php +++ b/inc/indexer.php @@ -338,6 +338,106 @@ class Doku_Indexer { return true; } + /** + * Rename a page in the search index without changing the indexed content + * + * @param string $oldpage The old page name + * @param string $newpage The new page name + * @return string|bool If the page was successfully renamed, can be a message in the case of an error + */ + public function renamePage($oldpage, $newpage) { + if (!$this->lock()) return 'locked'; + + $pages = $this->getPages(); + + $id = array_search($oldpage, $pages); + if ($id === false) { + $this->unlock(); + return 'page is not in index'; + } + + $new_id = array_search($newpage, $pages); + if ($new_id !== false) { + $this->unlock(); + // make sure the page is not in the index anymore + $this->deletePage($newpage); + if (!$this->lock()) return 'locked'; + + $pages[$new_id] = 'deleted:'.time().rand(0, 9999); + } + + $pages[$id] = $newpage; + + // update index + if (!$this->saveIndex('page', '', $pages)) { + $this->unlock(); + return false; + } + + // reset the pid cache + $this->pidCache = array(); + + $this->unlock(); + return true; + } + + /** + * Renames a meta value in the index. This doesn't change the meta value in the pages, it assumes that all pages + * will be updated. + * + * @param string $key The metadata key of which a value shall be changed + * @param string $oldvalue The old value that shall be renamed + * @param string $newvalue The new value to which the old value shall be renamed, can exist (then values will be merged) + * @return bool|string If renaming the value has been successful, false or error message on error. + */ + public function renameMetaValue($key, $oldvalue, $newvalue) { + if (!$this->lock()) return 'locked'; + + // change the relation references index + $metavalues = $this->getIndex($key, '_w'); + $oldid = array_search($oldvalue, $metavalues); + if ($oldid !== false) { + $newid = array_search($newvalue, $metavalues); + if ($newid !== false) { + // free memory + unset ($metavalues); + + // okay, now we have two entries for the same value. we need to merge them. + $indexline = $this->getIndexKey($key, '_i', $oldid); + if ($indexline != '') { + $newindexline = $this->getIndexKey($key, '_i', $newid); + $pagekeys = $this->getIndex($key, '_p'); + $parts = explode(':', $indexline); + foreach ($parts as $part) { + list($id, $count) = explode('*', $part); + $newindexline = $this->updateTuple($newindexline, $id, $count); + + $keyline = explode(':', $pagekeys[$id]); + // remove old meta value + $keyline = array_diff($keyline, array($oldid)); + // add new meta value when not already present + if (!in_array($newid, $keyline)) { + array_push($keyline, $newid); + } + $pagekeys[$id] = implode(':', $keyline); + } + $this->saveIndex($key, '_p', $pagekeys); + unset($pagekeys); + $this->saveIndexKey($key, '_i', $oldid, ''); + $this->saveIndexKey($key, '_i', $newid, $newindexline); + } + } else { + $metavalues[$oldid] = $newvalue; + if (!$this->saveIndex($key, '_w', $metavalues)) { + $this->unlock(); + return false; + } + } + } + + $this->unlock(); + return true; + } /** * Remove a page from the index * -- cgit v1.2.3 From b21a57bf3a948df8baf78cc9dc5387bf27592de1 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Fri, 25 Jan 2013 22:28:22 +0100 Subject: Add test cases for indexer rename page/meta value methods --- _test/tests/inc/indexer_rename.test.php | 83 +++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 _test/tests/inc/indexer_rename.test.php diff --git a/_test/tests/inc/indexer_rename.test.php b/_test/tests/inc/indexer_rename.test.php new file mode 100644 index 000000000..c9825ae4e --- /dev/null +++ b/_test/tests/inc/indexer_rename.test.php @@ -0,0 +1,83 @@ +indexer = idx_get_indexer(); + $this->indexer->clear(); + + saveWikiText($this->old_id, 'Old test content', 'Created old test page for indexer rename test'); + idx_addPage($this->old_id); + } + + function test_rename_to_new_page() { + $newid = 'new_id_1'; + + $oldpid = $this->indexer->getPID($this->old_id); + + $this->indexer->renamePage($this->old_id, $newid); + io_rename(wikiFN($this->old_id), wikiFN($newid)); + + $this->assertNotEquals($this->indexer->getPID($this->old_id), $oldpid, 'PID for the old page unchanged after rename.'); + $this->assertEquals($this->indexer->getPID($newid), $oldpid, 'New page has not the old pid.'); + $query = array('old'); + $this->assertEquals(array('old' => array($newid => 1)), $this->indexer->lookup($query), '"Old" doesn\'t find the new page'); + } + + function test_rename_to_existing_page() { + $newid = 'existing_page'; + saveWikiText($newid, 'Existing content', 'Created page for move_to_existing_page'); + idx_addPage($newid); + + $oldpid = $this->indexer->getPID($this->old_id); + $existingpid = $this->indexer->getPID($newid); + + $this->indexer->renamePage($this->old_id, $newid); + + $this->assertNotEquals($this->indexer->getPID($this->old_id), $oldpid, 'PID for old page unchanged after rename.'); + $this->assertNotEquals($this->indexer->getPID($this->old_id), $existingpid, 'PID for old page is now PID of the existing page.'); + $this->assertEquals($this->indexer->getPID($newid), $oldpid, 'New page has not the old pid.'); + $query = array('existing'); + $this->assertEquals(array('existing' => array()), $this->indexer->lookup($query), 'Existing page hasn\'t been deleted from the index.'); + $query = array('old'); + $this->assertEquals(array('old' => array($newid => 1)), $this->indexer->lookup($query), '"Old" doesn\'t find the new page'); + } + + function test_meta_rename_to_new_value() { + $this->indexer->addMetaKeys($this->old_id, array('mkey' => 'old_value')); + + $this->indexer->renameMetaValue('mkey', 'old_value', 'new_value'); + $query = 'old_value'; + $this->assertEquals(array(), $this->indexer->lookupKey('mkey', $query), 'Page can still be found under old value.'); + $query = 'new_value'; + $this->assertEquals(array($this->old_id), $this->indexer->lookupKey('mkey', $query), 'Page can\'t be found under new value.'); + } + + function test_meta_rename_to_existing_value() { + $this->indexer->addMetaKeys($this->old_id, array('mkey' => array('old_value', 'new_value'))); + + saveWikiText('newvalue', 'Test page', ''); + idx_addPage('newvalue'); + $this->indexer->addMetaKeys('newvalue', array('mkey' => array('new_value'))); + + saveWikiText('oldvalue', 'Test page', ''); + idx_addPage('oldvalue'); + $this->indexer->addMetaKeys('oldvalue', array('mkey' => array('old_value'))); + + $this->indexer->renameMetaValue('mkey', 'old_value', 'new_value'); + $query = 'old_value'; + $this->assertEquals(array(), $this->indexer->lookupKey('mkey', $query), 'Page can still be found under old value.'); + $query = 'new_value'; + $result = $this->indexer->lookupKey('mkey', $query); + $this->assertContains($this->old_id, $result, 'Page with both values can\'t be found anymore'); + $this->assertContains('newvalue', $result, 'Page with new value can\'t be found anymore'); + $this->assertContains('oldvalue', $result, 'Page with only the old value can\'t be found anymore'); + } +} -- cgit v1.2.3 From b1cfdc5fd569b4dc4b06d4443191b1e2bff27567 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Sat, 26 Jan 2013 11:17:20 +0100 Subject: Indexer rename test cases: assert that renames succeed --- _test/tests/inc/indexer_rename.test.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_test/tests/inc/indexer_rename.test.php b/_test/tests/inc/indexer_rename.test.php index c9825ae4e..d8c456f8e 100644 --- a/_test/tests/inc/indexer_rename.test.php +++ b/_test/tests/inc/indexer_rename.test.php @@ -22,7 +22,7 @@ class indexer_rename_test extends DokuWikiTest { $oldpid = $this->indexer->getPID($this->old_id); - $this->indexer->renamePage($this->old_id, $newid); + $this->assertTrue($this->indexer->renamePage($this->old_id, $newid), 'Renaming the page to a new id failed'); io_rename(wikiFN($this->old_id), wikiFN($newid)); $this->assertNotEquals($this->indexer->getPID($this->old_id), $oldpid, 'PID for the old page unchanged after rename.'); @@ -39,7 +39,7 @@ class indexer_rename_test extends DokuWikiTest { $oldpid = $this->indexer->getPID($this->old_id); $existingpid = $this->indexer->getPID($newid); - $this->indexer->renamePage($this->old_id, $newid); + $this->assertTrue($this->indexer->renamePage($this->old_id, $newid), 'Renaming the page to an existing id failed'); $this->assertNotEquals($this->indexer->getPID($this->old_id), $oldpid, 'PID for old page unchanged after rename.'); $this->assertNotEquals($this->indexer->getPID($this->old_id), $existingpid, 'PID for old page is now PID of the existing page.'); @@ -53,7 +53,7 @@ class indexer_rename_test extends DokuWikiTest { function test_meta_rename_to_new_value() { $this->indexer->addMetaKeys($this->old_id, array('mkey' => 'old_value')); - $this->indexer->renameMetaValue('mkey', 'old_value', 'new_value'); + $this->assertTrue($this->indexer->renameMetaValue('mkey', 'old_value', 'new_value'), 'Meta value rename to new value failed.'); $query = 'old_value'; $this->assertEquals(array(), $this->indexer->lookupKey('mkey', $query), 'Page can still be found under old value.'); $query = 'new_value'; @@ -71,7 +71,7 @@ class indexer_rename_test extends DokuWikiTest { idx_addPage('oldvalue'); $this->indexer->addMetaKeys('oldvalue', array('mkey' => array('old_value'))); - $this->indexer->renameMetaValue('mkey', 'old_value', 'new_value'); + $this->assertTrue($this->indexer->renameMetaValue('mkey', 'old_value', 'new_value'), 'Meta value rename to existing value failed'); $query = 'old_value'; $this->assertEquals(array(), $this->indexer->lookupKey('mkey', $query), 'Page can still be found under old value.'); $query = 'new_value'; -- cgit v1.2.3 From af73bba62fb11d7872a8b108b156d451302695bd Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Sat, 26 Jan 2013 11:17:59 +0100 Subject: Clarified the behavior of the Doku_Indexer::renamePage method --- inc/indexer.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inc/indexer.php b/inc/indexer.php index 70eac035b..37ca92055 100644 --- a/inc/indexer.php +++ b/inc/indexer.php @@ -339,7 +339,9 @@ class Doku_Indexer { } /** - * Rename a page in the search index without changing the indexed content + * Rename a page in the search index without changing the indexed content. This function doesn't check if the + * old or new name exists in the filesystem. It returns an error if the old page isn't in the page list of the + * indexer and it deletes all previously indexed content of the new page. * * @param string $oldpage The old page name * @param string $newpage The new page name -- cgit v1.2.3 From 25adeb91ff207452ebd6275707b8a0cc3121db6c Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Sat, 26 Jan 2013 11:18:52 +0100 Subject: Indexer: added internal deletePageNoLock method The new deletePageNoLock method is used by renamePage and avoids that the index needs to be unlocked and locked again for deleting the page. --- inc/indexer.php | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/inc/indexer.php b/inc/indexer.php index 37ca92055..c08e438bf 100644 --- a/inc/indexer.php +++ b/inc/indexer.php @@ -360,10 +360,8 @@ class Doku_Indexer { $new_id = array_search($newpage, $pages); if ($new_id !== false) { - $this->unlock(); // make sure the page is not in the index anymore - $this->deletePage($newpage); - if (!$this->lock()) return 'locked'; + $this->deletePageNoLock($newpage); $pages[$new_id] = 'deleted:'.time().rand(0, 9999); } @@ -440,6 +438,7 @@ class Doku_Indexer { $this->unlock(); return true; } + /** * Remove a page from the index * @@ -453,10 +452,26 @@ class Doku_Indexer { if (!$this->lock()) return "locked"; + $result = $this->deletePageNoLock($page); + + $this->unlock(); + + return $result; + } + + /** + * Remove a page from the index without locking the index, only use this function if the index is already locked + * + * Erases entries in all known indexes. + * + * @param string $page a page name + * @return boolean the function completed successfully + * @author Tom N Harris + */ + protected function deletePageNoLock($page) { // load known documents $pid = $this->getPIDNoLock($page); if ($pid === false) { - $this->unlock(); return false; } @@ -482,7 +497,6 @@ class Doku_Indexer { } // Save the reverse index if (!$this->saveIndexKey('pageword', '', $pid, "")) { - $this->unlock(); return false; } @@ -499,7 +513,6 @@ class Doku_Indexer { $this->saveIndexKey($metaname.'_p', '', $pid, ''); } - $this->unlock(); return true; } -- cgit v1.2.3 From bc27f3e28790e9a25e9428ed275624578b3e9a2d Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Sat, 26 Jan 2013 11:22:52 +0100 Subject: Indexer: abort page rename if deletion of new id fails --- inc/indexer.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inc/indexer.php b/inc/indexer.php index c08e438bf..e518907d7 100644 --- a/inc/indexer.php +++ b/inc/indexer.php @@ -361,7 +361,9 @@ class Doku_Indexer { $new_id = array_search($newpage, $pages); if ($new_id !== false) { // make sure the page is not in the index anymore - $this->deletePageNoLock($newpage); + if ($this->deletePageNoLock($newpage) !== true) { + return false; + } $pages[$new_id] = 'deleted:'.time().rand(0, 9999); } -- cgit v1.2.3