From 6dabff149dd9cdfc236b37ed31e939b52924db88 Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Thu, 28 Nov 2019 18:55:41 +0100 Subject: [PATCH 01/13] feat: first add of index with HashIndex --- src/JamesMoss/Flywheel/Config.php | 5 +- src/JamesMoss/Flywheel/Index/HashIndex.php | 52 ++++++ .../Flywheel/Index/IndexInterface.php | 46 ++++++ src/JamesMoss/Flywheel/Index/StoredIndex.php | 154 ++++++++++++++++++ src/JamesMoss/Flywheel/Repository.php | 53 ++++-- .../Flywheel/Index/HashIndexTest.php | 124 ++++++++++++++ test/JamesMoss/Flywheel/TestBase.php | 10 +- 7 files changed, 431 insertions(+), 13 deletions(-) create mode 100644 src/JamesMoss/Flywheel/Index/HashIndex.php create mode 100644 src/JamesMoss/Flywheel/Index/IndexInterface.php create mode 100644 src/JamesMoss/Flywheel/Index/StoredIndex.php create mode 100644 test/JamesMoss/Flywheel/Index/HashIndexTest.php diff --git a/src/JamesMoss/Flywheel/Config.php b/src/JamesMoss/Flywheel/Config.php index 333686d..2f01f21 100644 --- a/src/JamesMoss/Flywheel/Config.php +++ b/src/JamesMoss/Flywheel/Config.php @@ -48,12 +48,13 @@ public function getPath() * Gets a specific option from the config * * @param string $name The name of the option to return. + * @param mixed $default The default value to use. * * @return mixed The value of the option if it exists or null if it doesnt. */ - public function getOption($name) + public function getOption($name, $default = null) { - return isset($this->options[$name]) ? $this->options[$name] : null; + return isset($this->options[$name]) ? $this->options[$name] : $default; } public function hasAPC() diff --git a/src/JamesMoss/Flywheel/Index/HashIndex.php b/src/JamesMoss/Flywheel/Index/HashIndex.php new file mode 100644 index 0000000..d970301 --- /dev/null +++ b/src/JamesMoss/Flywheel/Index/HashIndex.php @@ -0,0 +1,52 @@ +data->$value)) { + return array(); + } + return array_keys(get_object_vars($this->data->$value)); + } + + /** + * @inheritdoc + */ + protected function addEntry($id, $value) + { + if (!isset($this->data->$value)) { + $this->data->$value = new stdClass(); + } + $this->data->$value->$id = 1; + } + + /** + * @inheritdoc + */ + protected function removeEntry($id, $value) + { + if(!isset($this->data->$value)) { + return; + } + unset($this->data->$value->$id); + if (count(get_object_vars($this->data->$value)) === 0) { + unset($this->data->$value); + } + } + + protected function updateEntry($id, $new, $old) + { + $this->removeEntry($id, $old); + $this->addEntry($id, $new); + } +} diff --git a/src/JamesMoss/Flywheel/Index/IndexInterface.php b/src/JamesMoss/Flywheel/Index/IndexInterface.php new file mode 100644 index 0000000..52690dd --- /dev/null +++ b/src/JamesMoss/Flywheel/Index/IndexInterface.php @@ -0,0 +1,46 @@ + a list of documents ids. + */ + public function get($value); + + /** + * Add a document to the index. + * + * @param string $id the id of this document. + * @param mixed $value the value of this document for the indexed field. + * + * @return void + */ + public function add($id, $value); + + /** + * Remove a document from the index. + * + * @param string $id the id of this document. + * @param mixed $value the value of this document for the indexed field. + * + * @return void + */ + public function remove($id, $value); + + /** + * Update a document in the index. + * + * @param string $id the id of this document. + * @param mixed $new the new value of this document for the indexed field. + * @param mixed $old the old value of this document for the indexed field. + * + * @return void + */ + public function update($id, $new, $old); +} diff --git a/src/JamesMoss/Flywheel/Index/StoredIndex.php b/src/JamesMoss/Flywheel/Index/StoredIndex.php new file mode 100644 index 0000000..3214889 --- /dev/null +++ b/src/JamesMoss/Flywheel/Index/StoredIndex.php @@ -0,0 +1,154 @@ +field = $field; + $this->formatter = $formatter; + $this->repository = $repository; + $this->path = $path . DIRECTORY_SEPARATOR . "$field." . $formatter->getFileExtension(); + } + + /** + * @inheritdoc + */ + public function get($value) + { + $this->needsData(); + return $this->getEntries($value); + } + + /** + * @inheritdoc + */ + public function add($id, $value) + { + $this->needsData(); + $this->addEntry($id, $value); + $this->flush(); + } + + /** + * @inheritdoc + */ + public function remove($id, $value) + { + $this->needsData(); + $this->removeEntry($id, $value); + $this->flush(); + } + + /** + * @inheritdoc + */ + public function update($id, $new, $old) + { + if ($new === $old) { + return; + } + $this->needsData(); + $this->updateEntry($id, $new, $old); + $this->flush(); + } + + protected function needsData() + { + if (isset($this->data)) { + return; + } + $this->data = new stdClass(); + if (file_exists($this->path)) { + $fp = fopen($this->path, 'r'); + $contents = fread($fp, filesize($this->path)); + fclose($fp); + $this->data = $this->formatter->decode($contents); + } else { + $field = $this->field; + foreach ($this->repository->findAll() as $doc) { + if (empty($doc->$field)) { + continue; + } + $this->addEntry($doc->getId(), $doc->$field); + } + $this->flush(); + } + } + + protected function flush() + { + $contents = $this->formatter->encode(get_object_vars($this->data)); + $fp = fopen($this->path, 'w'); + if(!flock($fp, LOCK_EX)) { + return false; + } + $result = fwrite($fp, $contents); + flock($fp, LOCK_UN); + fclose($fp); + + return $result !== false; + } + + /** + * Get entries from the index + * + * @param string $value + * + * @return array array of ids + */ + abstract protected function getEntries($value); + + /** + * Adds an entry in the index + * + * @param string $id + * @param string $value + */ + abstract protected function addEntry($id, $value); + + /** + * Removes an entry from the index + * + * @param string $id + * @param string $value + */ + abstract protected function removeEntry($id, $value); + + /** + * Removes an entry from the index + * + * @param string $id + * @param string $value + */ + abstract protected function updateEntry($id, $new, $old); + +} diff --git a/src/JamesMoss/Flywheel/Repository.php b/src/JamesMoss/Flywheel/Repository.php index c4463d4..0287d3a 100644 --- a/src/JamesMoss/Flywheel/Repository.php +++ b/src/JamesMoss/Flywheel/Repository.php @@ -2,6 +2,9 @@ namespace JamesMoss\Flywheel; +use JamesMoss\Flywheel\Formatter\JSON; +use JamesMoss\Flywheel\Index\IndexInterface; + /** * Repository * @@ -15,6 +18,9 @@ class Repository protected $formatter; protected $queryClass; protected $documentClass; + protected $indexDir; + /** @var array $indexes */ + protected $indexes; /** * Constructor @@ -30,17 +36,28 @@ public function __construct($name, Config $config) $this->formatter = $config->getOption('formatter'); $this->queryClass = $config->getOption('query_class'); $this->documentClass = $config->getOption('document_class'); + $this->indexes = $config->getOption('indexes', array()); + $this->indexDir = $this->path . DIRECTORY_SEPARATOR . 'index'; + array_walk($this->indexes, function(&$class, $field) { + $class = new $class($field, $this->indexDir, new JSON(), $this); + }); // Ensure the repo name is valid $this->validateName($this->name); + $this->ensureDirectory($this->path); + $this->ensureDirectory($this->indexDir); + } - // Ensure directory exists and we can write there - if (!is_dir($this->path)) { - if (!@mkdir($this->path, 0777, true)) { - throw new \RuntimeException(sprintf('`%s` doesn\'t exist and can\'t be created.', $this->path)); + /** + * Ensure directory exists and we can write there + */ + protected function ensureDirectory($path) { + if (!is_dir($path)) { + if (!@mkdir($path, 0777, true)) { + throw new \RuntimeException(sprintf('`%s` doesn\'t exist and can\'t be created.', $path)); } - } else if (!is_writable($this->path)) { - throw new \RuntimeException(sprintf('`%s` is not writable.', $this->path)); + } else if (!is_writable($path)) { + throw new \RuntimeException(sprintf('`%s` is not writable.', $path)); } } @@ -79,7 +96,7 @@ public function query() /** * Returns all the documents within this repo. * - * @return array An array of Documents. + * @return array An array of Documents. */ public function findAll() { @@ -141,7 +158,7 @@ public function findById($id) * * @param Document $document The document to store * - * @return bool True if stored, otherwise false + * @return string|false True if stored, otherwise false */ public function store(DocumentInterface $document) { @@ -155,6 +172,18 @@ public function store(DocumentInterface $document) if (!$this->validateId($id)) { throw new \Exception(sprintf('`%s` is not a valid document ID.', $id)); } + $previous = $this->findById($id); + foreach ($this->indexes as $field => $index) { + $setPrev = $previous ? isset($previous->$field) : false; + $setActu = isset($document->$field); + if (!$setPrev && $setActu) { + $index->add($document->getId(), $document->$field); + } elseif ($setPrev && !$setActu) { + $index->remove($document->getId(), $previous->$field); + } elseif ($setPrev && $setActu) { + $index->update($document->getId(), $document->$field, $previous->$field); + } + } $path = $this->getPathForDocument($id); $data = get_object_vars($document); @@ -189,9 +218,13 @@ public function update(DocumentInterface $document) // If the ID has changed we need to delete the old document. if($document->getId() !== $document->getInitialId()) { - if(file_exists($oldPath)) { - unlink($oldPath); + $previous = $this->findById($document->getInitialId()); + foreach ($this->indexes as $field => $index) { + if(isset($previous->$field)) { + $index->remove($previous->getId(), $previous->$field); + } } + unlink($oldPath); } return $this->store($document); diff --git a/test/JamesMoss/Flywheel/Index/HashIndexTest.php b/test/JamesMoss/Flywheel/Index/HashIndexTest.php new file mode 100644 index 0000000..eea60ae --- /dev/null +++ b/test/JamesMoss/Flywheel/Index/HashIndexTest.php @@ -0,0 +1,124 @@ + array( + 'col1' => "JamesMoss\Flywheel\Index\HashIndex", + ) + )); + $this->repo = new Repository(self::REPO_NAME, $config); + $this->index = new HashIndex('col1', $this->repo->getPath() . DIRECTORY_SEPARATOR . 'index', new JSON(), $this->repo); + } + + protected function tearDown() + { + parent::tearDown(); + $this->recurseRmdir(self::REPO_DIR . DIRECTORY_SEPARATOR . self::REPO_NAME); + } + + public function testAddEntry() + { + $id = 'testdoc123'; + $this->index->add($id, '123'); + $this->assertEquals(array($id), $this->index->get('123')); + } + + public function testRemoveEntry() + { + $id = 'testdoc123'; + $this->index->add($id, '123'); + $this->assertEquals(array($id), $this->index->get('123')); + $this->index->remove($id, '123'); + $this->assertEquals(array(), $this->index->get('123')); + } + + public function testUpdateEntry() + { + $id = 'testdoc123'; + $this->index->add($id, '123'); + $this->assertEquals(array($id), $this->index->get('123')); + $this->index->update($id, '456', '123'); + $this->assertEquals(array(), $this->index->get('123')); + $this->assertEquals(array($id), $this->index->get('456')); + } + + public function testStoreDocument() + { + $doc = new Document(array( + 'col1' => '123', + )); + $id = 'testdoc123'; + $doc->setId($id); + $this->assertEquals($id, $this->repo->store($doc)); + $this->assertEquals(array($id), $this->index->get('123')); + } + + public function testReStoreDocumentUpdate() + { + $doc = new Document(array( + 'col1' => '123', + )); + $id = 'testdoc123'; + $doc->setId($id); + $this->assertEquals($id, $this->repo->store($doc)); + $doc->col1 = '456'; + $this->assertEquals($id, $this->repo->store($doc)); + $this->assertEquals(array(), $this->index->get('123')); + $this->assertEquals(array($id), $this->index->get('456')); + } + + public function testReStoreDocumentRemove() + { + $doc = new Document(array( + 'col1' => '123', + )); + $id = 'testdoc123'; + $doc->setId($id); + $this->assertEquals($id, $this->repo->store($doc)); + unset($doc->col1); + $this->assertEquals($id, $this->repo->store($doc)); + $this->assertEquals(array(), $this->index->get('123')); + } + + public function testUpdateDocument() + { + $doc = new Document(array( + 'col1' => '123', + )); + $id = 'testdoc123'; + $doc->setId($id); + $this->assertEquals($id, $this->repo->store($doc)); + $id = 'testdoc456'; + $doc->setId($id); + $doc->col1 = '456'; + $this->assertEquals($id, $this->repo->update($doc)); + $this->assertEquals(array(), $this->index->get('123')); + $this->assertEquals(array($id), $this->index->get('456')); + } + +} \ No newline at end of file diff --git a/test/JamesMoss/Flywheel/TestBase.php b/test/JamesMoss/Flywheel/TestBase.php index c4bb75b..82f4619 100644 --- a/test/JamesMoss/Flywheel/TestBase.php +++ b/test/JamesMoss/Flywheel/TestBase.php @@ -7,5 +7,13 @@ class TestBase extends \PHPUnit\Framework\TestCase public function normalizeLineendings($content) { return str_replace("\r\n", "\n", $content); - } + } + + public function recurseRmdir($dir) { + $files = array_diff(scandir($dir), array('.','..')); + foreach ($files as $file) { + (is_dir("$dir/$file")) ? $this->recurseRmdir("$dir/$file") : unlink("$dir/$file"); + } + return rmdir($dir); + } } \ No newline at end of file From 5c49aadbfe389e057c81994c98bb48f630d34959 Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Fri, 29 Nov 2019 14:04:33 +0100 Subject: [PATCH 02/13] feat: working query executor on fullindex query --- src/JamesMoss/Flywheel/Index/HashIndex.php | 27 +- .../Flywheel/Index/IndexInterface.php | 12 +- src/JamesMoss/Flywheel/Index/StoredIndex.php | 38 ++- src/JamesMoss/Flywheel/Predicate.php | 4 +- src/JamesMoss/Flywheel/QueryExecuter.php | 85 +++++- src/JamesMoss/Flywheel/Repository.php | 45 ++- .../Flywheel/Index/HashIndexTest.php | 101 ++++++- test/JamesMoss/Flywheel/QueryExecuterTest.php | 21 +- test/JamesMoss/Flywheel/RepositoryTest.php | 54 +++- test/JamesMoss/Flywheel/TestBase.php | 41 ++- .../querytest/countries/.indexes/region.json | 264 ++++++++++++++++++ 11 files changed, 640 insertions(+), 52 deletions(-) create mode 100644 test/JamesMoss/Flywheel/fixtures/datastore/querytest/countries/.indexes/region.json diff --git a/src/JamesMoss/Flywheel/Index/HashIndex.php b/src/JamesMoss/Flywheel/Index/HashIndex.php index d970301..9f73435 100644 --- a/src/JamesMoss/Flywheel/Index/HashIndex.php +++ b/src/JamesMoss/Flywheel/Index/HashIndex.php @@ -7,16 +7,34 @@ class HashIndex extends StoredIndex { + protected static $operators = array( + '==', '===', '!=', '!==' + ); /** * @inheritdoc */ - protected function getEntries($value) + public function isOperatorCompatible($operator) + { + return in_array($operator, self::$operators); + } + + /** + * @inheritdoc + */ + protected function getEntries($value, $operator) { if (!isset($this->data->$value)) { return array(); } - return array_keys(get_object_vars($this->data->$value)); + $ids = array_keys(get_object_vars($this->data->$value)); + switch ($operator) { + case '==': + case '===': return $ids; + case '!=': + case '!==': return array_diff(get_object_vars($this->data), $ids); + default: throw new \InvalidArgumentException('Incompatible operator `'.$operator.'`.'); + } } /** @@ -35,7 +53,7 @@ protected function addEntry($id, $value) */ protected function removeEntry($id, $value) { - if(!isset($this->data->$value)) { + if (!isset($this->data->$value)) { return; } unset($this->data->$value->$id); @@ -44,6 +62,9 @@ protected function removeEntry($id, $value) } } + /** + * @inheritdoc + */ protected function updateEntry($id, $new, $old) { $this->removeEntry($id, $old); diff --git a/src/JamesMoss/Flywheel/Index/IndexInterface.php b/src/JamesMoss/Flywheel/Index/IndexInterface.php index 52690dd..7481d78 100644 --- a/src/JamesMoss/Flywheel/Index/IndexInterface.php +++ b/src/JamesMoss/Flywheel/Index/IndexInterface.php @@ -4,14 +4,24 @@ interface IndexInterface { + /** + * Checks if the given operator is compatible with this index. + * + * @param string $operator the operator to check. + * + * @return bool true if it is compatible. + */ + public function isOperatorCompatible($operator); + /** * Get documents from the index. * * @param mixed $value the value we are looking for. + * @param string $operator the operator used for comparision. * * @return array a list of documents ids. */ - public function get($value); + public function get($value, $operator); /** * Add a document to the index. diff --git a/src/JamesMoss/Flywheel/Index/StoredIndex.php b/src/JamesMoss/Flywheel/Index/StoredIndex.php index 3214889..e82312f 100644 --- a/src/JamesMoss/Flywheel/Index/StoredIndex.php +++ b/src/JamesMoss/Flywheel/Index/StoredIndex.php @@ -4,6 +4,8 @@ use JamesMoss\Flywheel\Index\IndexInterface; use JamesMoss\Flywheel\Formatter\FormatInterface; +use JamesMoss\Flywheel\Predicate; +use JamesMoss\Flywheel\QueryExecuter; use JamesMoss\Flywheel\Repository; use stdClass; @@ -32,7 +34,8 @@ abstract class StoredIndex implements IndexInterface * @param FormatInterface $formatter the formatter used to store the data. * @param Repository $repository the repository of this index. */ - public function __construct($field, $path, $formatter, $repository) { + public function __construct($field, $path, $formatter, $repository) + { $this->field = $field; $this->formatter = $formatter; $this->repository = $repository; @@ -42,10 +45,15 @@ public function __construct($field, $path, $formatter, $repository) { /** * @inheritdoc */ - public function get($value) + abstract public function isOperatorCompatible($operator); + + /** + * @inheritdoc + */ + public function get($value, $operator) { $this->needsData(); - return $this->getEntries($value); + return $this->getEntries($value, $operator); } /** @@ -81,6 +89,11 @@ public function update($id, $new, $old) $this->flush(); } + /** + * Lazyloading data initializer. + * + * @return void + */ protected function needsData() { if (isset($this->data)) { @@ -94,21 +107,30 @@ protected function needsData() $this->data = $this->formatter->decode($contents); } else { $field = $this->field; + $predicate = new Predicate(); + $qe = new QueryExecuter($this->repository, $predicate->where($field, '=='), array(), array()); foreach ($this->repository->findAll() as $doc) { - if (empty($doc->$field)) { + $docVal = $qe->getFieldValue($doc, $field, $found); + + if (!$found) { continue; } - $this->addEntry($doc->getId(), $doc->$field); + $this->addEntry($doc->getId(), $docVal); } $this->flush(); } } + /** + * Write back the data on the filesystem. + * + * @return bool succeded. + */ protected function flush() { $contents = $this->formatter->encode(get_object_vars($this->data)); $fp = fopen($this->path, 'w'); - if(!flock($fp, LOCK_EX)) { + if (!flock($fp, LOCK_EX)) { return false; } $result = fwrite($fp, $contents); @@ -122,10 +144,11 @@ protected function flush() * Get entries from the index * * @param string $value + * @param string $operator * * @return array array of ids */ - abstract protected function getEntries($value); + abstract protected function getEntries($value, $operator); /** * Adds an entry in the index @@ -150,5 +173,4 @@ abstract protected function removeEntry($id, $value); * @param string $value */ abstract protected function updateEntry($id, $new, $old); - } diff --git a/src/JamesMoss/Flywheel/Predicate.php b/src/JamesMoss/Flywheel/Predicate.php index 7efe0ec..8d75d4c 100644 --- a/src/JamesMoss/Flywheel/Predicate.php +++ b/src/JamesMoss/Flywheel/Predicate.php @@ -14,7 +14,7 @@ class Predicate const LOGICAL_OR = 'or'; protected $predicates = array(); - protected $operators = array( + protected static $operators = array( '>', '>=', '<', '<=', '==', '===', '!=', '!==', 'IN', ); @@ -63,7 +63,7 @@ protected function addPredicate($type, $field, $operator = null, $value = null) throw new \InvalidArgumentException('Field name cannot be empty.'); } - if (!in_array($operator, $this->operators)) { + if (!in_array($operator, self::$operators)) { throw new \InvalidArgumentException('Unknown operator `'.$operator.'`.'); } diff --git a/src/JamesMoss/Flywheel/QueryExecuter.php b/src/JamesMoss/Flywheel/QueryExecuter.php index 9d9e296..d3628f5 100644 --- a/src/JamesMoss/Flywheel/QueryExecuter.php +++ b/src/JamesMoss/Flywheel/QueryExecuter.php @@ -14,6 +14,7 @@ class QueryExecuter protected $predicate; protected $limit; protected $orderBy; + protected $indexes; /** * Constructor @@ -29,6 +30,7 @@ public function __construct(Repository $repo, Predicate $pred, array $limit, arr $this->predicate = $pred; $this->limit = $limit; $this->orderBy = $orderBy; + $this->indexes = $repo->getIndexes(); } /** @@ -38,10 +40,17 @@ public function __construct(Repository $repo, Predicate $pred, array $limit, arr */ public function run() { - $documents = $this->repo->findAll(); - + /** @var array $documents */ + $documents; if ($predicates = $this->predicate->getAll()) { - $documents = $this->filter($documents, $predicates); + if ($this->isFullIndex($predicates)) { + $documents = $this->findByIndex($predicates); + } else { + $documents = $this->repo->findAll(); + $documents = $this->filter($documents, $predicates); + } + } else { + $documents = $this->repo->findAll(); } if ($this->orderBy) { @@ -114,6 +123,32 @@ public function matchDocument($doc, $field, $operator, $value) return false; } + /** + * Checks if the query can be executed with indexes only. + * + * @param array $predicates the array of predicates. + * + * @return bool true if it can. + */ + protected function isFullIndex($predicates) + { + foreach ($predicates as $p) { + list($type, $field, $operator) = $p; + if (!isset($this->indexes[$field]) || !$this->indexes[$field]->isOperatorCompatible($operator)) { + return false; + } + } + return true; + } + + /** + * Filters an array of documents by the predicates. + * + * @param array $documents the array to filter. + * @param array $predicates the array of predicates. + * + * @return array the filtered array of documents. + */ protected function filter($documents, $predicates) { $result = array(); @@ -162,6 +197,50 @@ protected function filter($documents, $predicates) return $result; } + /** + * Find an array of documents from the predicates using the indexes. + * + * @param array $predicates the array of predicates. + * + * @return array the filtered array of documents. + */ + protected function findByIndex($predicates) { + $result = array(); + $ids = array(); + + $andPredicates = array_filter($predicates, function($pred) { + return $pred[0] !== Predicate::LOGICAL_OR; + }); + + $orPredicates = array_filter($predicates, function($pred) { + return $pred[0] === Predicate::LOGICAL_OR; + }); + + foreach($andPredicates as $predicate) { + if (is_array($predicate[1])) { + $ids = $this->findByIndex($predicate[1]); + } else { + list($type, $field, $operator, $value) = $predicate; + $ids = $this->indexes[$field]->get($value, $operator); + } + + $result = $ids; + } + + foreach($orPredicates as $predicate) { + if (is_array($predicate[1])) { + $ids = $this->findByIndex($predicate[1]); + } else { + list($type, $field, $operator, $value) = $predicate; + $ids = $this->indexes['$field']->get($value, $operator); + } + + $result = array_unique(array_merge($result, $ids), SORT_REGULAR); + } + + return $this->repo->findByIds($result); + } + /** * Sorts an array of documents by multiple fields if needed. * diff --git a/src/JamesMoss/Flywheel/Repository.php b/src/JamesMoss/Flywheel/Repository.php index 0287d3a..5982b1e 100644 --- a/src/JamesMoss/Flywheel/Repository.php +++ b/src/JamesMoss/Flywheel/Repository.php @@ -37,7 +37,7 @@ public function __construct($name, Config $config) $this->queryClass = $config->getOption('query_class'); $this->documentClass = $config->getOption('document_class'); $this->indexes = $config->getOption('indexes', array()); - $this->indexDir = $this->path . DIRECTORY_SEPARATOR . 'index'; + $this->indexDir = $this->path . DIRECTORY_SEPARATOR . '.indexes'; array_walk($this->indexes, function(&$class, $field) { $class = new $class($field, $this->indexDir, new JSON(), $this); }); @@ -81,6 +81,16 @@ public function getPath() return $this->path; } + /** + * Returns the list of indexes of this repository. + * + * @return array The list of indexes. + */ + public function getIndexes() + { + return $this->indexes; + } + /** * A factory method that initialises and returns an instance of a Query object. * @@ -127,7 +137,7 @@ public function findAll() * * @param string $id The ID of the document to find * - * @return Document|boolean The document if it exists, false if not. + * @return Document|false The document if it exists, false if not. */ public function findById($id) { @@ -153,6 +163,37 @@ public function findById($id) return $doc; } + /** + * Returns a list of documents based on their ID. + * + * @param array $ids The IDs array of document to find. + * + * @return array|false An array of Documents. + */ + public function findByIds($ids) + { + $ext = $this->formatter->getFileExtension(); + $documents = array(); + foreach ($ids as $id) { + if(!file_exists($path = $this->getPathForDocument($id))) { + return false; + } + $fp = fopen($path, 'r'); + $contents = fread($fp, filesize($path)); + fclose($fp); + + $data = $this->formatter->decode($contents); + + if (null !== $data) { + $doc = new $this->documentClass((array) $data); + $doc->setId($this->getIdFromPath($path, $ext)); + + $documents[] = $doc; + } + } + return $documents; + } + /** * Store a Document in the repository. * diff --git a/test/JamesMoss/Flywheel/Index/HashIndexTest.php b/test/JamesMoss/Flywheel/Index/HashIndexTest.php index eea60ae..7fbcc35 100644 --- a/test/JamesMoss/Flywheel/Index/HashIndexTest.php +++ b/test/JamesMoss/Flywheel/Index/HashIndexTest.php @@ -11,8 +11,9 @@ class HashIndexTest extends TestBase { - const REPO_DIR = '/tmp/flywheel'; + const REPO_DIR = '/tmp/flywheel'; const REPO_NAME = '_index_test'; + const REPO_PATH = '/tmp/flywheel/_index_test/'; /** @var Repository $repo */ private $repo; @@ -32,39 +33,39 @@ protected function setUp() ) )); $this->repo = new Repository(self::REPO_NAME, $config); - $this->index = new HashIndex('col1', $this->repo->getPath() . DIRECTORY_SEPARATOR . 'index', new JSON(), $this->repo); + $this->index = new HashIndex('col1', self::REPO_PATH . '.indexes', new JSON(), $this->repo); } protected function tearDown() { parent::tearDown(); - $this->recurseRmdir(self::REPO_DIR . DIRECTORY_SEPARATOR . self::REPO_NAME); + $this->recurseRmdir(self::REPO_PATH); } public function testAddEntry() { $id = 'testdoc123'; $this->index->add($id, '123'); - $this->assertEquals(array($id), $this->index->get('123')); + $this->assertEquals(array($id), $this->index->get('123', '==')); } public function testRemoveEntry() { $id = 'testdoc123'; $this->index->add($id, '123'); - $this->assertEquals(array($id), $this->index->get('123')); + $this->assertEquals(array($id), $this->index->get('123', '==')); $this->index->remove($id, '123'); - $this->assertEquals(array(), $this->index->get('123')); + $this->assertEquals(array(), $this->index->get('123', '==')); } public function testUpdateEntry() { $id = 'testdoc123'; $this->index->add($id, '123'); - $this->assertEquals(array($id), $this->index->get('123')); + $this->assertEquals(array($id), $this->index->get('123', '==')); $this->index->update($id, '456', '123'); - $this->assertEquals(array(), $this->index->get('123')); - $this->assertEquals(array($id), $this->index->get('456')); + $this->assertEquals(array(), $this->index->get('123', '==')); + $this->assertEquals(array($id), $this->index->get('456', '==')); } public function testStoreDocument() @@ -75,7 +76,32 @@ public function testStoreDocument() $id = 'testdoc123'; $doc->setId($id); $this->assertEquals($id, $this->repo->store($doc)); - $this->assertEquals(array($id), $this->index->get('123')); + $this->assertEquals(array($id), $this->index->get('123', '==')); + } + + public function testReStoreDocumentNochange() + { + $doc = new Document(array( + 'col1' => '123', + )); + $id = 'testdoc123'; + $doc->setId($id); + $this->assertEquals($id, $this->repo->store($doc)); + $this->assertEquals($id, $this->repo->store($doc)); + $this->assertEquals(array($id), $this->index->get('123', '==')); + } + + public function testReStoreDocumentAdd() + { + $doc = new Document(array( + 'col2' => '123', + )); + $id = 'testdoc123'; + $doc->setId($id); + $this->assertEquals($id, $this->repo->store($doc)); + $doc->col1 = '456'; + $this->assertEquals($id, $this->repo->store($doc)); + $this->assertEquals(array($id), $this->index->get('456', '==')); } public function testReStoreDocumentUpdate() @@ -88,8 +114,8 @@ public function testReStoreDocumentUpdate() $this->assertEquals($id, $this->repo->store($doc)); $doc->col1 = '456'; $this->assertEquals($id, $this->repo->store($doc)); - $this->assertEquals(array(), $this->index->get('123')); - $this->assertEquals(array($id), $this->index->get('456')); + $this->assertEquals(array(), $this->index->get('123', '==')); + $this->assertEquals(array($id), $this->index->get('456', '==')); } public function testReStoreDocumentRemove() @@ -102,7 +128,7 @@ public function testReStoreDocumentRemove() $this->assertEquals($id, $this->repo->store($doc)); unset($doc->col1); $this->assertEquals($id, $this->repo->store($doc)); - $this->assertEquals(array(), $this->index->get('123')); + $this->assertEquals(array(), $this->index->get('123', '==')); } public function testUpdateDocument() @@ -117,8 +143,53 @@ public function testUpdateDocument() $doc->setId($id); $doc->col1 = '456'; $this->assertEquals($id, $this->repo->update($doc)); - $this->assertEquals(array(), $this->index->get('123')); - $this->assertEquals(array($id), $this->index->get('456')); + $this->assertEquals(array(), $this->index->get('123', '==')); + $this->assertEquals(array($id), $this->index->get('456', '==')); + } + + public function testExistingData() + { + $repo = new Repository(self::REPO_NAME, new Config( + self::REPO_DIR + )); + $doc = new Document(array( + 'col1' => '123', + )); + $id = 'testdoc123'; + $doc->setId($id); + $this->assertEquals($id, $repo->store($doc)); + $this->assertEquals(array($id), $this->index->get('123', '==')); + } + + public function testDeepKey() + { + $id = 'testdoc123456'; + $doc = new Document(array( + 'col1' => '123', + 'col2' => array('4', '5', '6'), + )); + $doc->setId($id); + + $repo2 = new Repository(self::REPO_NAME, new Config( + self::REPO_DIR, array( + 'indexes' => array( + 'col2.0' => "JamesMoss\Flywheel\Index\HashIndex", + ) + ) + )); + + $this->assertEquals($id, $this->repo->store($doc)); + $this->assertEquals($id, $repo2->store($doc)); + + // test generating index from fs + $index1 = new HashIndex('col2.0', self::REPO_PATH . '.indexes', new JSON(), $this->repo); + $this->assertEquals(array($id), $index1->get('4', '==')); + + // test generating index on store document + $index2 = $repo2->getIndexes()['col2.0']; + $this->assertEquals(array($id), $index2->get('4', '==')); + + } } \ No newline at end of file diff --git a/test/JamesMoss/Flywheel/QueryExecuterTest.php b/test/JamesMoss/Flywheel/QueryExecuterTest.php index 19d6e1b..47cd456 100644 --- a/test/JamesMoss/Flywheel/QueryExecuterTest.php +++ b/test/JamesMoss/Flywheel/QueryExecuterTest.php @@ -193,11 +193,28 @@ public function testOrderingById() $this->assertEquals('Djibouti', $result[2]->name); } + public function testFindByIndex() + { + $pred = $this->getPredicate() + ->where('region', '==', 'Europe'); + $options = array( + 'indexes' => array( + 'region' => '\JamesMoss\Flywheel\Index\HashIndex' + ) + ); + $qe = new QueryExecuter($this->getRepo('countries', $options), $pred, array(), array()); + $withIndex = $qe->run(); + $qe = new QueryExecuter($this->getRepo('countries'), $pred, array(), array()); + $withoutIndex = $qe->run(); + $this->assertSameSize($withoutIndex, $withIndex); + $this->assertEqualsUnordered(get_object_vars($withoutIndex), get_object_vars($withIndex)); + } + - protected function getRepo($repoName) + protected function getRepo($repoName, $options = array()) { $path = __DIR__ . '/fixtures/datastore/querytest'; - $config = new Config($path . '/'); + $config = new Config($path . '/', $options); return new Repository($repoName, $config); } diff --git a/test/JamesMoss/Flywheel/RepositoryTest.php b/test/JamesMoss/Flywheel/RepositoryTest.php index 164a610..49c192a 100644 --- a/test/JamesMoss/Flywheel/RepositoryTest.php +++ b/test/JamesMoss/Flywheel/RepositoryTest.php @@ -6,6 +6,10 @@ class RespositoryTest extends TestBase { + const REPO_DIR = '/tmp/flywheel'; + const REPO_NAME = '_pages'; + const REPO_PATH = '/tmp/flywheel/_pages/'; + /** @var Repository $repo */ private $repo; @@ -13,12 +17,17 @@ class RespositoryTest extends TestBase protected function setUp() { parent::setUp(); - - if (!is_dir('/tmp/flywheel')) { - mkdir('/tmp/flywheel'); + if (!is_dir(self::REPO_DIR)) { + mkdir(self::REPO_DIR); } - $config = new Config('/tmp/flywheel'); - $this->repo = new Repository('_pages', $config); + $config = new Config(self::REPO_DIR); + $this->repo = new Repository(self::REPO_NAME, $config); + } + + protected function tearDown() + { + parent::tearDown(); + $this->recurseRmdir(self::REPO_PATH); } /** @@ -26,9 +35,10 @@ protected function setUp() */ public function testValidRepoName($name) { - $config = new Config('/tmp'); + $config = new Config(self::REPO_DIR); $repo = new Repository($name, $config); $this->assertSame($name, $repo->getName()); + $this->recurseRmdir($repo->getPath()); } /** @@ -37,7 +47,7 @@ public function testValidRepoName($name) */ public function testInvalidRepoName($name) { - $config = new Config('/tmp'); + $config = new Config(self::REPO_DIR); new Repository($name, $config); } @@ -107,7 +117,7 @@ public function testStoringDocuments() $repo->store($document); $name = $i . '.json'; - $this->assertSame($data, (array) json_decode(file_get_contents('/tmp/flywheel/_pages/' . $name))); + $this->assertSame($data, (array) json_decode(file_get_contents(self::REPO_PATH . $name))); } } @@ -116,7 +126,7 @@ public function testDeletingDocuments() $repo = $this->repo; $id = 'delete_test'; $name = $id . '.json'; - $path = '/tmp/flywheel/_pages/' . $name; + $path = self::REPO_PATH . $name; file_put_contents($path, ''); @@ -138,7 +148,7 @@ public function testRenamingDocumentChangesDocumentID() $repo->store($doc); - rename('/tmp/flywheel/_pages/testdoc123.json', '/tmp/flywheel/_pages/newname.json'); + rename(self::REPO_PATH . 'testdoc123.json', self::REPO_PATH . 'newname.json'); foreach ($repo->findAll() as $document) { if ('newname' === $document->getId()) { @@ -160,12 +170,32 @@ public function testChangingDocumentIDChangesFilename() $doc->setId('test1234'); $repo->store($doc); - $this->assertTrue(file_exists('/tmp/flywheel/_pages/test1234.json')); + $this->assertTrue(file_exists(self::REPO_PATH . 'test1234.json')); $doc->setId('9876test'); $repo->update($doc); - $this->assertFalse(file_exists('/tmp/flywheel/_pages/test1234.json')); + $this->assertFalse(file_exists(self::REPO_PATH . 'test1234.json')); + } + + public function testFindByIds() + { + for ($i=0; $i < 10; $i++) { + $doc = new Document(array( + 'test' => $i, + )); + $doc->setId("doc$i"); + $this->repo->store($doc); + } + $docs = $this->repo->findByIds(array('doc1', 'doc3', 'doc4')); + $this->assertCount(3, $docs); + $this->assertEquals(1, $docs[0]->test); + $this->assertEquals(3, $docs[1]->test); + $this->assertEquals(4, $docs[2]->test); + $docs = $this->repo->findByIds(array('doc1', 'DOC3', 'doc4')); + $this->assertFalse($docs); + $docs = $this->repo->findByIds(array()); + $this->assertCount(0, $docs); } // public function testLockingOnWrite() diff --git a/test/JamesMoss/Flywheel/TestBase.php b/test/JamesMoss/Flywheel/TestBase.php index 82f4619..0505df9 100644 --- a/test/JamesMoss/Flywheel/TestBase.php +++ b/test/JamesMoss/Flywheel/TestBase.php @@ -9,11 +9,44 @@ public function normalizeLineendings($content) return str_replace("\r\n", "\n", $content); } - public function recurseRmdir($dir) { - $files = array_diff(scandir($dir), array('.','..')); + public function recurseRmdir($dir) + { + $files = array_diff(scandir($dir), array('.', '..')); foreach ($files as $file) { - (is_dir("$dir/$file")) ? $this->recurseRmdir("$dir/$file") : unlink("$dir/$file"); + (is_dir("$dir/$file")) ? $this->recurseRmdir("$dir/$file") : unlink("$dir/$file"); } return rmdir($dir); } -} \ No newline at end of file + + private function arraysEqualsUnordered($a, $b) + { + // if the indexes don't match, return immediately + if (count(array_diff_assoc($a, $b))) { + return false; + } + // we know that the indexes, but maybe not values, match. + // compare the values between the two arrays + foreach ($a as $k => $v) { + if ($v !== $b[$k]) { + return false; + } + } + // we have identical indexes, and no unequal values + return true; + } + + /** + * Determine if two associative arrays are similar + * + * Both arrays must have the same indexes with identical values + * without respect to key ordering + * + * @param array $expected + * @param array $actual + * @return bool + */ + public function assertEqualsUnordered($expected, $actual) + { + $this->assertTrue($this->arraysEqualsUnordered($expected, $actual)); + } +} diff --git a/test/JamesMoss/Flywheel/fixtures/datastore/querytest/countries/.indexes/region.json b/test/JamesMoss/Flywheel/fixtures/datastore/querytest/countries/.indexes/region.json new file mode 100644 index 0000000..ed4e178 --- /dev/null +++ b/test/JamesMoss/Flywheel/fixtures/datastore/querytest/countries/.indexes/region.json @@ -0,0 +1,264 @@ +{ + "Europe": { + "Isle of Man_76733f338bb21fd91ee8304d05accb9163ee6e52": 1, + "Montenegro_479fb34bdbf4aba90d03cca83f64ad4e912cc854": 1, + "Hungary_f14e46ce7d094f9326167acc499698128651be85": 1, + "Bosnia and Herzegovina_62ac59b7255ce5ff9fee8ac49157bdd9bc4445e2": 1, + "Germany_17d53e0e6a68acdf80b78d4f9d868c8736db2cec": 1, + "Faroe Islands_1082f89115626b9c431f2176f832b3a7cc60fb2c": 1, + "Monaco_35ab1c3de6dac148401654e1ebf5dd935ccefa14": 1, + "Romania_d6b897fd145a64fbad36ff7cb1c47a00dcbbe9b6": 1, + "Netherlands_fb61c8a8eba24117016597fc7618f38821b16e8f": 1, + "Republic of Kosovo_338e718d14c93382811119b87ed77b25aaed8a98": 1, + "Russia_6754fe3cd8310d20ed04b8c7b66abebcdb16d88d": 1, + "France_e3772ac4b4db87b4a8dbfa59ef43cd1a8ad29515": 1, + "Croatia_d7e0453bb4af87006533f4d77ad9546dac533db8": 1, + "Estonia_f0a96da3f86a334cbe8d569110d6545277973859": 1, + "Vatican City_136f5db5de74430b7c1e66fcd4ef0b6a70ace276": 1, + "Greece_4902a456caa9a4eab463ce526c9df0f6180be184": 1, + "Belarus_027a12c2fc8568e8b70b07ff536faf288a013670": 1, + "Andorra_9d3bd1fb52785a7baeb03b437e7b2ecba54ef34f": 1, + "Macedonia_41349e9557e537855ae0f63cc43918fa13a561d3": 1, + "Guernsey_180c97abdb80833b91cb32903dced0e8f8b49562": 1, + "San Marino_87fd3befcce67042618f49ec496c2b09a49459fa": 1, + "Czech Republic_1fef42909247a6c230eeef66277f2b5e0e8f274e": 1, + "Svalbard and Jan Mayen_ac99404d12d60aa0c46631720dd7afa750d0e909": 1, + "Jersey_ff4636d0caf61d70e7dfa437d80aab75e921e76c": 1, + "Norway_988455e67df7cd81d090ea4bacdc05f39fb7caa5": 1, + "Portugal_a495190bcc2ef4116725616d321016a7def5bd8f": 1, + "Iceland_b3c92eecf0aa1905086059d9f6d3261d8fb19657": 1, + "Moldova_9791bc4d7273587d61fef79841bd342381f5e321": 1, + "United Kingdom_9769121f10f77079b27eb08e9ffa488cbcc37ed0": 1, + "Switzerland_77dcd849e550afec3c83d38fcc8cbc72c058f4db": 1, + "Belgium_5cb4c9d828175ed3931ec52305b32f47173a8e04": 1, + "Latvia_c5f5bb3b350774d7cda57104c55fb6c82b7ae7d9": 1, + "Spain_20a8df9b760336178fca425339ec1c7e542a2463": 1, + "Finland_c909b138eba89ecfbd86df4c9d170ac78d4a3820": 1, + "Austria_593905b31972f6ffe58325abf98595caf4ebf458": 1, + "Liechtenstein_b0ddce0f54c916c106117e280aead4f9c0cbf1df": 1, + "Italy_ad79ef0f076d3a686ab9738925f4dd2c7e69d7d1": 1, + "Lithuania_74a788cee27d549015a0786732c662e05cdd7567": 1, + "Albania_79b9d273ac6d2488109d1ea43e2bdb7977bd2b28": 1, + "Malta_1a591a3e91fcb7a47f2c08e9e2e117f39af22078": 1, + "Denmark_89da124e04dfe1ad9946cd37d91a119e1d028898": 1, + "Luxembourg_5076721c4060feeb69bd2c3dd9bdce115d5c62f3": 1, + "Serbia_6d31bf00d7eddc6a617a6b16699f8ba91794e2fd": 1, + "Ukraine_c951ec00f123510a00d1e3d9539b11b4631d4096": 1, + "Slovenia_d1aa0503612aa4168939b77b59ca74532a11951a": 1, + "Sweden_72ddd2b619af6d6a73febf80f7fcad22495498cd": 1, + "A\u030aland Islands_065154080d2f7539638e616e64cfcdb36c0577a1": 1, + "Ireland_eb2131ece0efe78ee8bb1ae98af6099114a8df09": 1, + "Slovakia_b6c149c3e00467fba347629a63ed02fed098d061": 1, + "Poland_5ff03b7273b1808e5ba852e230991bbf07da703c": 1, + "Bulgaria_5c77726358c5daf98ad9cdccd0882bca0f718b88": 1, + "Gibraltar_e51335897c0ef4bb952693d4166902146a1bc812": 1 + }, + "Americas": { + "Chile_349507e41dd8c71c10c9df6d2444b5e64a285691": 1, + "Saint Pierre and Miquelon_70e53283edb6d53accd21aa7731dfe1f66246b84": 1, + "Jamaica_5eedd6a16ab862cb5d6b2e194e0bdf8b0161d89a": 1, + "Argentina_354bf98925838ca68611b950e2a37ebd11c21640": 1, + "Uruguay_66b98924b384b40aea844bf0fe399d5d3832388e": 1, + "Bonaire_fd71a444cd1566324317cbc5204af645857b5b72": 1, + "Saint Kitts and Nevis_3ad2dd829664a86f29aec0770e244c1717c79255": 1, + "Costa Rica_1bf429f94068620b112aca3888b58aecbc2eadee": 1, + "Suriname_b17fc6f05c078b63528a6d19c2abe0917e1e8a1b": 1, + "Saint Vincent and the Grenadines_3fa9b2dd85d217dd4f6407fbb779928701f9e184": 1, + "French Guiana_07090aa7d613105dad9bd80005cd91b8da1c4cb2": 1, + "Montserrat_35f005157cfd133875b06f037eeeff3b7494456c": 1, + "Trinidad and Tobago_4d206a8103d3d20ac685314dbba1c88f6cfedd85": 1, + "South Georgia_618cd353b39276e0dd2215fabbbbb4d21803a69c": 1, + "Paraguay_71a45296474d608f35d4b49f3b10384fc738e16f": 1, + "Bahamas_1f797564f7843d36ebe5e841e5bd39c98157e22b": 1, + "Saint Lucia_673b1cf863c8e2d10109731b098b19ba81aec9fb": 1, + "United States Virgin Islands_65f5cb3322400cfd62ba3c8bcffbd7a8d71d0f32": 1, + "Turks and Caicos Islands_611bc125b7ad66ad9d5103c6bfd968a775763e6c": 1, + "Haiti_a4842f0234d7270b757b60b6f17a1b9f4d560dad": 1, + "Falkland Islands_11499959510cbab21e5241a8a6099a3d545b5227": 1, + "Puerto Rico_fe23c52dc2441843aa073092188089600dad336c": 1, + "Aruba_f3a826101b9a25f3573cb0f1a7505587de8c65c3": 1, + "Honduras_5aa588714ab4cbfd615d238fd9778c3a14ca4ba8": 1, + "Bermuda_027ed37f00fb38adb089a91f2d52c7b931949168": 1, + "Anguilla_5722849f2ccb586368a07473e71e8df1bae7a221": 1, + "Curac\u0327ao_9461dd94ebb7643ea4fbbed9dc781497e05a6e7e": 1, + "Panama_1e36b31e788231fba03577144de1d23b04a5d324": 1, + "United States_768685ca582abd0af2fbb57ca37752aa98c9372b": 1, + "Peru_36c57243155b80cf350c59764354b20dde157333": 1, + "Nicaragua_4812648c8a890c1818305642f6a01fa134b34401": 1, + "Cuba_c484b137e2d18229e4e7e677f1f8cfce6a0ab819": 1, + "United States Minor Outlying Islands_ede34a33eb69cc5980a8c2353752463771b7060e": 1, + "Guadeloupe_f0f524ff3b9cf7a30a94a59841b9c0d2fd858b56": 1, + "British Virgin Islands_1fc1b5b29cefd14d32c7e252ca38633927f0c3ee": 1, + "Dominican Republic_8a4bf12e17b2be590b12721ef8d5d0248698b5b6": 1, + "Colombia_2f737399606486655401cb27b066f4658424766d": 1, + "Saint Martin_854ccc29cc22ef957d1e5ce961f30c1e92677d9c": 1, + "Antigua and Barbuda_530670dca74039e859436ac4734296b861556efd": 1, + "Belize_42ab0c94a1e3bd6175a15dba215ccf5f10e861e5": 1, + "Ecuador_09f199d25132204e99bfb1a89916de24494f19bb": 1, + "Dominica_bc1cd4f07d828493332f9c95c491d6fb338afd40": 1, + "Greenland_1ba0cfa550295f8b2c5fa44b8236639adc825cf9": 1, + "Mexico_41937b20fbe8c71d9c6c3346aff43c001aa25e33": 1, + "El Salvador_259a6e935b848823a0c0b76aced2803060b4bd0c": 1, + "Guyana_bc88a24030a15bde613c8749ed93d30e0a81fcc2": 1, + "Sint Maarten_ad7c3cc73f9cd1c049d3208aac984c49953b94b2": 1, + "Guatemala_11760e1aac4396e10d315e93ad3df3e99204dc5e": 1, + "Grenada_dbf2a2da6458b242407285e7f1483aeb6cfee9fd": 1, + "Martinique_66f1a98a952d50fc3164cde58265772e2ab821c2": 1, + "Bolivia_a001af75ee89582f31cb4db6d3dd0b4766c80050": 1, + "Barbados_93409af2f208e7545f0f26996e048113edd88652": 1, + "Venezuela_9d4ac43d5e24e3d01e448ce01a277ba5971e60f6": 1, + "Canada_cd6a7b8768528485a0dbcd459185091e80dc28ad": 1, + "Saint Barthe\u0301lemy_f48e864508243ec01f6ac7ff781f13cd5bde0a0f": 1, + "Cayman Islands_e9e21c35ab345ef790305554368ebf78279482f2": 1, + "Brazil_37497aa5a2272c49714aee1b07e8edf973a95f59": 1 + }, + "Africa": { + "Guinea_b47b54fd3a460f43ebbcabe5b10c189176f25f25": 1, + "Gabon_a06dcd71328fd2b44fa7f84012096e8f318e4b48": 1, + "Swaziland_c985df0809c3183c074ef84ac9e902d70ff81992": 1, + "Niger_6687e1896dd857dc1587782149127ff073a95696": 1, + "Lesotho_93b12bf57f18c13c9ad2f55e33a8e3fd786fc394": 1, + "Liberia_1ed5dd9d833f675b7509886681e2164d842f8dad": 1, + "Libya_55949d4c16632f1c275d7684a379b8f1717b3904": 1, + "Ethiopia_3d91f7631ba813c13a08a208f1255b2e96fb03d0": 1, + "Democratic Republic of the Congo_f39d28021812d3e91f4c64c9ac055a20d5bcfd30": 1, + "Morocco_32e087f099be121c1211285a1700ff83542193f7": 1, + "Cameroon_73a7ddd505f2fc2cead1522e54a794328f228c44": 1, + "Cape Verde_8e40809e474f3e0705ddef056618ce3e5043522a": 1, + "Botswana_180c89bf50c8b29deb45e49d9dac62fcd5c8bedb": 1, + "Seychelles_3d2d7b1c3400e9222f97a999decf652a6c34bdc5": 1, + "Co\u0302te d'Ivoire_b2182992301690448db9754c5493729921c28056": 1, + "Madagascar_f92bcb6a06d2ec7c0af7c8a338f131bf887c64a0": 1, + "Re\u0301union_8e7e261a1ff0a9bbf184ec58b7db9d014d425e65": 1, + "British Indian Ocean Territory_9bb835f4f91b31fae3286529520eecae620d718c": 1, + "Mayotte_dcc64425eed0e625072cc41ac60a88d4b3d50086": 1, + "Egypt_1c39abf68e93e438ae5dca946e2d6a986cccd3a9": 1, + "Sa\u0303o Tome\u0301 and Pri\u0301ncipe_655191a80835f37342e045bd71b73188430d3b39": 1, + "Central African Republic_dba86789b53e54df0477a589a57298893cd87502": 1, + "South Sudan_6981f4026cf83935b630141d375a9455fc18acd7": 1, + "Republic of the Congo_b78ae043a4412720001cad4a83c176d09da0437f": 1, + "Malawi_0ce65b26dcd3c08e1b329d9efbb6bbbced426f31": 1, + "Eritrea_18740af5bcec9573bd3c059e0fc6570353097aab": 1, + "Sudan_1193ba31f109ecfdfab76f13cc2b5e44479d5603": 1, + "Algeria_bd6acc8626d118aea60331ce33bf000c9d7d1cee": 1, + "Zimbabwe_5922f5ceeff38bcfdb993efd6cfd5c472f827fa9": 1, + "Ghana_317dbac90743c3e5e82b2ddf122cd076a2226a92": 1, + "Nigeria_9742d008c420ec9d44b1794c03b9701c477f93ff": 1, + "South Africa_35fda17ff05f63e9061208c2dd2aaaf98790e921": 1, + "Burkina Faso_e71b56d92347386fa76239ce33f67aaa2de52207": 1, + "Gambia_7c39974f44b2b12933c66a9eba3fe33c8d0805b6": 1, + "Saint Helena_03bdca149f934311777f23c15054c37a49e3289b": 1, + "Somalia_4dfdf195ecb76a3fa83788deca6fd3b289dd568f": 1, + "Equatorial Guinea_80eaf3087b0b041473c6c223f1e09197b649362e": 1, + "Tunisia_edf404d0db32652d41a29b428a804405e1d73a9e": 1, + "Uganda_e92904bce8026b3c1f8828b0ce882e6b081c7fb6": 1, + "Namibia_bfe79debcedda1c1e11f7836c0fc13ad22dffca7": 1, + "Kenya_a84f56f2e6a77ecb4b2f89344446dd3ff91b87c4": 1, + "Mozambique_a40a9be00a9ae956acd67011c0d96758916f40ea": 1, + "Mauritius_26160d23fb07cf8d5dae186eba322e9fc8e27bb4": 1, + "Senegal_d8973b8edfe5e3211044e39d5452523b5d69cba9": 1, + "Togo_30949d5f4a69766caa7abe5e6fd9993090b1b6b3": 1, + "Guinea-Bissau_b07cb9a2b24832a8197cd3dfa67d1d6adfb0cfbb": 1, + "Western Sahara_fab1a52391201253134c8cc0b956613cad675611": 1, + "Djibouti_60a8b0c6de6f6abe5999959a5c7352750116fb9c": 1, + "Mali_daa6a489dbca7a13c480aaa1d0c344957590fdb7": 1, + "Zambia_dcf25ed56c9566181e0f2d48d8854c04c4ca6b37": 1, + "Rwanda_7266a1da7e3a6739b245ddfe74b0b682f7da63f8": 1, + "Burundi_4617585b8749a71bbb21237cab6c2dc9cbe3b86f": 1, + "Benin_373616e39fb47d9a1a4e87dfd4ea968037435f14": 1, + "Chad_6c6b1b2b3ecc0e6900000dabf4faae6f8df5ffd1": 1, + "Angola_a42522a0cdd6e41a1379e6c95d08a9c46a17249c": 1, + "Comoros_e5bb59a2731998cae2070f6fd4f2e075fd61146f": 1, + "Tanzania_7e380be8dc28d72571144716e95e598c986bc4d6": 1, + "Sierra Leone_50df2d658581499ea96e34b53f6280fd7754bd1f": 1, + "Mauritania_85fa355bdb4f46fa53a20a441623d53d686d4036": 1 + }, + "Asia": { + "Armenia_5f4599daa3415f788c1afc3db145f01b4bd2b438": 1, + "Tajikistan_279c771ae60c33fc34bde4627bb56ee8eecca33a": 1, + "Bahrain_3ae11c725c30009d4d3418bc6b30789feed78322": 1, + "Lebanon_5caa7f811b5aff6eb9993f309c4c045785ee67ec": 1, + "Malaysia_ff3ea3bec182358766650a6fd2872d9221f7e6cc": 1, + "Timor-Leste_2d79f6dbae283f57f4092d5370a518c3ef5caec1": 1, + "Iraq_1aed9ecc6e1be8eaaaddc5ffb6ba3ed84a8b1ae6": 1, + "North Korea_765b46e79beff128df05b121d344cc7ad23dbac5": 1, + "Laos_7998be8d446d668c99abca446cb3bd79fce08d2b": 1, + "Nepal_0e1d589f58b71e2b7d5d8068156d160146820407": 1, + "Japan_fcf29f6cad3232704b33e962ef5194fad3b6817b": 1, + "Saudi Arabia_6f49c31192f5681e1053a1d699956998c5d7e97d": 1, + "Yemen_ac33d641f4c9d9de5b29bb95e7f909066cfca512": 1, + "Oman_c14c36dffca61b07410f5e3630ba5d99c60ab5d5": 1, + "Afghanistan_c69153687791fb52c12ce7cca2f4d03a65d9abf8": 1, + "Myanmar_928b7c48a60ad93b81bc3bee9d274c5f2aed9ad3": 1, + "Uzbekistan_db800e86e9f303e72e1102efb21d459512902d37": 1, + "Israel_4c197dfd67f1ed79d11a8b0218cc368bfcce6ccb": 1, + "India_967ce367d89dccc133d71049f1197d29561b3726": 1, + "Brunei_130e4a34f1b807a8f3bd24b204c06ec4de4010da": 1, + "Azerbaijan_213598a7e92217bee3a758f3c69aea09ed940c0e": 1, + "Bhutan_bb2254a806f43df24753ca390143e2ca8c1e4e80": 1, + "Qatar_83ef3e6cee83cb88a39814638a94fa1ad33e65a5": 1, + "United Arab Emirates_d115b8d5acb8386b8012aaf4cbb3812cacd97c8e": 1, + "Bangladesh_fa6c3752cd00f7f1277fd7e5604ab8d2edaf26b8": 1, + "Kyrgyzstan_c78791a3f0e109d34ba4eac2afa35ec011439ae3": 1, + "Palestine_05a15c18a2038b35edcdb52e92f002c65d4fcf32": 1, + "Syria_3ed104337eb1dfb750107978743cbf25fbe2c2b5": 1, + "Thailand_a2b7c120c93a01e67dcd4d984d2a781cde2c46df": 1, + "Cambodia_314ccd964ef6a8e68ac5f9bb89f751a1c2196c56": 1, + "Turkmenistan_1f8dc1a6076e90fb31efa48a19e2d220b647c81f": 1, + "Sri Lanka_197042c4de91dae8aeafa9a52dc8c3a59aa41dc0": 1, + "Cyprus_852addab901cbc5699d190285a009d7a7035fb57": 1, + "China_d2eaf2aa1512d6596e0a5bae633537c6b8e779a3": 1, + "Mongolia_f54da380bade5d43098fd1f338cb8257523c508b": 1, + "Kazakhstan_2f36b6bb1852c24392fb3ee9a2879da24eb0750d": 1, + "Iran_889224e3fca24a6ab17d01fe47a45bc82244e938": 1, + "South Korea_04653e650280742f94a5a74ad6530ee09c2dc2be": 1, + "Vietnam_681101d8f9fa4e5e1fbe8ac5a2c05504c6c4875a": 1, + "Indonesia_35536a41b209715d9e3ad440431fef2672f20bbe": 1, + "Singapore_20c0b7bdab70ca2cc9c844a0d74a3af0bbf41c3e": 1, + "Kuwait_93295b0c76b900da760a8e0f2e9a29a1ba4b0f4c": 1, + "Georgia_9113c6c0c1f9cb53e3543b53136ba30c51018373": 1, + "Macao_918c53a2d6129e9e4f42191b60fa11886bd9fa50": 1, + "Taiwan_094d515b3608fefc6759a36412cee467437417a5": 1, + "Pakistan_82d220df17cebf5ce4897d780c354dd5e925c209": 1, + "Philippines_8067364d44f5e37baba7e13ba124e934df410e2a": 1, + "Jordan_674027e17b0ed64e76cde2005cb8e76fb4cd671a": 1, + "Turkey_d7153e6702b4ea48c7c0d01affdef0e1b39fd6dc": 1, + "Maldives_213cb204509284ff2aedeca9290b70a6da307eab": 1, + "Hong Kong_2f488ffc82ccc67a1616e39fdfc537297f1646c5": 1 + }, + "Oceania": { + "Palau_e5e0b68adfacb66979505bedf6d424070c536eb6": 1, + "Fiji_bbb7ef7f2ec9557d0895c9da1c5cddd50d15049e": 1, + "Papua New Guinea_fbaa3c3e9e1bbf9fe813f2d4c038b295ef046512": 1, + "Cook Islands_4bacfe2535ecbceb9a04a1b85c3a2e0bc53c64d6": 1, + "Christmas Island_c6dfa97a611dbc45bfdc51c69a762488964e6361": 1, + "French Polynesia_40f39cdcd3acd771d31e4269e54caeff9b3b5edf": 1, + "Pitcairn Islands_0311d743a395837f9490fa8a89013d4b047b366e": 1, + "Tokelau_a08c1ca2fca39f1ea2de01c2de009926d07afb4d": 1, + "Northern Mariana Islands_28a117f173634e3c574231e0cfb011aabfd2bba8": 1, + "Guam_f8aa3a934ee38a6c86d1b092624a9c385267d927": 1, + "Kiribati_3f57adf2c06cecd095ff83ab72787889961bbe87": 1, + "Marshall Islands_bb130626b42c3cce860334424aef9a144f8231f2": 1, + "Tuvalu_9a2248f7c4de37c13c8e2688356857b589f669b4": 1, + "American Samoa_ca0b36fec74bc61226adce2b5ce0e8ef6fdca179": 1, + "Nauru_f648c72a69c3a9b157c3603ebd4a75141a833e46": 1, + "Tonga_e8a1234b8442f09d8965e48d93a16e10fa398baa": 1, + "Australia_ceafb51e2b0783d53dd620019dff3aa66708a26f": 1, + "Wallis and Futuna_a187509aa2afa6ef9b1abbaed04d4bb5b6124ffb": 1, + "Norfolk Island_fedb20736102194ca53af14fae03929409c27b9c": 1, + "New Zealand_a2238e91907fa2436a6a50e0fd8cf97ab60ec508": 1, + "Niue_c635e30148c3a013261cd3147f9ff8c70b2f9fea": 1, + "Solomon Islands_ecae78abec522d0fa00ddb3666aa9db9baf0265f": 1, + "Samoa_f5680604ea9e0abbe3833339ee5a793c2c02551d": 1, + "Micronesia_5a7d7bc91620c2307f0ad338a4b7d7929bb8af8e": 1, + "Cocos (Keeling) Islands_d2dd2d43fef0a1aa180b01e58e8a9c32d7ac165a": 1, + "Vanuatu_d16ad2dcc6af8252a554ea5f1722abc518dfa3b1": 1, + "New Caledonia_b9967e88983d851428fa745aa72bb8b4e0bec2da": 1 + }, + "": { + "Bouvet Island_290125ef9fc28ca6df6137e0523169786f3ecfea": 1, + "Antarctica_00f33fc530d3e011ee6ab56f206622e221888971": 1, + "French Southern and Antarctic Lands_44e46ba30eabfe96165ac52eb27f1318dd7396e4": 1, + "Heard Island and McDonald Islands_130447083d0bf6b6914b952751355ad34949129b": 1 + } +} \ No newline at end of file From e7775eb265e4c873fdfd659f8c89e5fac0109d18 Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Sun, 1 Dec 2019 13:47:51 +0100 Subject: [PATCH 03/13] tests: better testFindByIndex --- test/JamesMoss/Flywheel/QueryExecuterTest.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/JamesMoss/Flywheel/QueryExecuterTest.php b/test/JamesMoss/Flywheel/QueryExecuterTest.php index 47cd456..a17ccc8 100644 --- a/test/JamesMoss/Flywheel/QueryExecuterTest.php +++ b/test/JamesMoss/Flywheel/QueryExecuterTest.php @@ -202,12 +202,25 @@ public function testFindByIndex() 'region' => '\JamesMoss\Flywheel\Index\HashIndex' ) ); + $n = 5; + $qe = new QueryExecuter($this->getRepo('countries', $options), $pred, array(), array()); - $withIndex = $qe->run(); + $start = microtime(true); + for ($i=0; $i < $n; $i++) { + $withIndex = $qe->run(); + } + $timeWithIndex = microtime(true) - $start; + $qe = new QueryExecuter($this->getRepo('countries'), $pred, array(), array()); - $withoutIndex = $qe->run(); + $start = microtime(true); + for ($i=0; $i < $n; $i++) { + $withoutIndex = $qe->run(); + } + $timeWithoutIndex = microtime(true) - $start; + $this->assertSameSize($withoutIndex, $withIndex); $this->assertEqualsUnordered(get_object_vars($withoutIndex), get_object_vars($withIndex)); + $this->assertLessThan($timeWithoutIndex, $timeWithIndex); } From 0afd3552153495f94ed41074f2ced08f8451df6e Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Sun, 1 Dec 2019 15:27:18 +0100 Subject: [PATCH 04/13] feat: add option parameter to JSON formatter to use an ugly json encoding for the indexes --- src/JamesMoss/Flywheel/Formatter/JSON.php | 11 +- .../JamesMoss/Flywheel/Formatter/JSONTest.php | 8 +- .../querytest/countries/.indexes/region.json | 265 +----------------- 3 files changed, 13 insertions(+), 271 deletions(-) diff --git a/src/JamesMoss/Flywheel/Formatter/JSON.php b/src/JamesMoss/Flywheel/Formatter/JSON.php index d848aaa..4fe3c16 100644 --- a/src/JamesMoss/Flywheel/Formatter/JSON.php +++ b/src/JamesMoss/Flywheel/Formatter/JSON.php @@ -4,6 +4,13 @@ class JSON implements FormatInterface { + protected $jsonOptions; + + public function __construct($jsonOptions = 0) + { + $this->jsonOptions = $jsonOptions; + } + public function getFileExtension() { return 'json'; @@ -11,9 +18,7 @@ public function getFileExtension() public function encode(array $data) { - $options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : null; - - return json_encode($data, $options); + return json_encode($data, $this->jsonOptions); } public function decode($data) diff --git a/test/JamesMoss/Flywheel/Formatter/JSONTest.php b/test/JamesMoss/Flywheel/Formatter/JSONTest.php index 56a132b..2bca33e 100644 --- a/test/JamesMoss/Flywheel/Formatter/JSONTest.php +++ b/test/JamesMoss/Flywheel/Formatter/JSONTest.php @@ -14,16 +14,16 @@ public function testFileExtension() public function testEncoding() { - $formatter = new JSON; + $formatter = new JSON(); + $formatterPretty = new JSON(JSON_PRETTY_PRINT); $data = array( 'name' => 'Joe', 'age' => 21, 'employed' => true, ); - $options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : null; - - $this->assertSame(json_encode($data, $options), $formatter->encode($data)); + $this->assertSame(json_encode($data), $formatter->encode($data)); + $this->assertSame(json_encode($data, JSON_PRETTY_PRINT), $formatterPretty->encode($data)); } public function testDecoding() diff --git a/test/JamesMoss/Flywheel/fixtures/datastore/querytest/countries/.indexes/region.json b/test/JamesMoss/Flywheel/fixtures/datastore/querytest/countries/.indexes/region.json index ed4e178..b521fa8 100644 --- a/test/JamesMoss/Flywheel/fixtures/datastore/querytest/countries/.indexes/region.json +++ b/test/JamesMoss/Flywheel/fixtures/datastore/querytest/countries/.indexes/region.json @@ -1,264 +1 @@ -{ - "Europe": { - "Isle of Man_76733f338bb21fd91ee8304d05accb9163ee6e52": 1, - "Montenegro_479fb34bdbf4aba90d03cca83f64ad4e912cc854": 1, - "Hungary_f14e46ce7d094f9326167acc499698128651be85": 1, - "Bosnia and Herzegovina_62ac59b7255ce5ff9fee8ac49157bdd9bc4445e2": 1, - "Germany_17d53e0e6a68acdf80b78d4f9d868c8736db2cec": 1, - "Faroe Islands_1082f89115626b9c431f2176f832b3a7cc60fb2c": 1, - "Monaco_35ab1c3de6dac148401654e1ebf5dd935ccefa14": 1, - "Romania_d6b897fd145a64fbad36ff7cb1c47a00dcbbe9b6": 1, - "Netherlands_fb61c8a8eba24117016597fc7618f38821b16e8f": 1, - "Republic of Kosovo_338e718d14c93382811119b87ed77b25aaed8a98": 1, - "Russia_6754fe3cd8310d20ed04b8c7b66abebcdb16d88d": 1, - "France_e3772ac4b4db87b4a8dbfa59ef43cd1a8ad29515": 1, - "Croatia_d7e0453bb4af87006533f4d77ad9546dac533db8": 1, - "Estonia_f0a96da3f86a334cbe8d569110d6545277973859": 1, - "Vatican City_136f5db5de74430b7c1e66fcd4ef0b6a70ace276": 1, - "Greece_4902a456caa9a4eab463ce526c9df0f6180be184": 1, - "Belarus_027a12c2fc8568e8b70b07ff536faf288a013670": 1, - "Andorra_9d3bd1fb52785a7baeb03b437e7b2ecba54ef34f": 1, - "Macedonia_41349e9557e537855ae0f63cc43918fa13a561d3": 1, - "Guernsey_180c97abdb80833b91cb32903dced0e8f8b49562": 1, - "San Marino_87fd3befcce67042618f49ec496c2b09a49459fa": 1, - "Czech Republic_1fef42909247a6c230eeef66277f2b5e0e8f274e": 1, - "Svalbard and Jan Mayen_ac99404d12d60aa0c46631720dd7afa750d0e909": 1, - "Jersey_ff4636d0caf61d70e7dfa437d80aab75e921e76c": 1, - "Norway_988455e67df7cd81d090ea4bacdc05f39fb7caa5": 1, - "Portugal_a495190bcc2ef4116725616d321016a7def5bd8f": 1, - "Iceland_b3c92eecf0aa1905086059d9f6d3261d8fb19657": 1, - "Moldova_9791bc4d7273587d61fef79841bd342381f5e321": 1, - "United Kingdom_9769121f10f77079b27eb08e9ffa488cbcc37ed0": 1, - "Switzerland_77dcd849e550afec3c83d38fcc8cbc72c058f4db": 1, - "Belgium_5cb4c9d828175ed3931ec52305b32f47173a8e04": 1, - "Latvia_c5f5bb3b350774d7cda57104c55fb6c82b7ae7d9": 1, - "Spain_20a8df9b760336178fca425339ec1c7e542a2463": 1, - "Finland_c909b138eba89ecfbd86df4c9d170ac78d4a3820": 1, - "Austria_593905b31972f6ffe58325abf98595caf4ebf458": 1, - "Liechtenstein_b0ddce0f54c916c106117e280aead4f9c0cbf1df": 1, - "Italy_ad79ef0f076d3a686ab9738925f4dd2c7e69d7d1": 1, - "Lithuania_74a788cee27d549015a0786732c662e05cdd7567": 1, - "Albania_79b9d273ac6d2488109d1ea43e2bdb7977bd2b28": 1, - "Malta_1a591a3e91fcb7a47f2c08e9e2e117f39af22078": 1, - "Denmark_89da124e04dfe1ad9946cd37d91a119e1d028898": 1, - "Luxembourg_5076721c4060feeb69bd2c3dd9bdce115d5c62f3": 1, - "Serbia_6d31bf00d7eddc6a617a6b16699f8ba91794e2fd": 1, - "Ukraine_c951ec00f123510a00d1e3d9539b11b4631d4096": 1, - "Slovenia_d1aa0503612aa4168939b77b59ca74532a11951a": 1, - "Sweden_72ddd2b619af6d6a73febf80f7fcad22495498cd": 1, - "A\u030aland Islands_065154080d2f7539638e616e64cfcdb36c0577a1": 1, - "Ireland_eb2131ece0efe78ee8bb1ae98af6099114a8df09": 1, - "Slovakia_b6c149c3e00467fba347629a63ed02fed098d061": 1, - "Poland_5ff03b7273b1808e5ba852e230991bbf07da703c": 1, - "Bulgaria_5c77726358c5daf98ad9cdccd0882bca0f718b88": 1, - "Gibraltar_e51335897c0ef4bb952693d4166902146a1bc812": 1 - }, - "Americas": { - "Chile_349507e41dd8c71c10c9df6d2444b5e64a285691": 1, - "Saint Pierre and Miquelon_70e53283edb6d53accd21aa7731dfe1f66246b84": 1, - "Jamaica_5eedd6a16ab862cb5d6b2e194e0bdf8b0161d89a": 1, - "Argentina_354bf98925838ca68611b950e2a37ebd11c21640": 1, - "Uruguay_66b98924b384b40aea844bf0fe399d5d3832388e": 1, - "Bonaire_fd71a444cd1566324317cbc5204af645857b5b72": 1, - "Saint Kitts and Nevis_3ad2dd829664a86f29aec0770e244c1717c79255": 1, - "Costa Rica_1bf429f94068620b112aca3888b58aecbc2eadee": 1, - "Suriname_b17fc6f05c078b63528a6d19c2abe0917e1e8a1b": 1, - "Saint Vincent and the Grenadines_3fa9b2dd85d217dd4f6407fbb779928701f9e184": 1, - "French Guiana_07090aa7d613105dad9bd80005cd91b8da1c4cb2": 1, - "Montserrat_35f005157cfd133875b06f037eeeff3b7494456c": 1, - "Trinidad and Tobago_4d206a8103d3d20ac685314dbba1c88f6cfedd85": 1, - "South Georgia_618cd353b39276e0dd2215fabbbbb4d21803a69c": 1, - "Paraguay_71a45296474d608f35d4b49f3b10384fc738e16f": 1, - "Bahamas_1f797564f7843d36ebe5e841e5bd39c98157e22b": 1, - "Saint Lucia_673b1cf863c8e2d10109731b098b19ba81aec9fb": 1, - "United States Virgin Islands_65f5cb3322400cfd62ba3c8bcffbd7a8d71d0f32": 1, - "Turks and Caicos Islands_611bc125b7ad66ad9d5103c6bfd968a775763e6c": 1, - "Haiti_a4842f0234d7270b757b60b6f17a1b9f4d560dad": 1, - "Falkland Islands_11499959510cbab21e5241a8a6099a3d545b5227": 1, - "Puerto Rico_fe23c52dc2441843aa073092188089600dad336c": 1, - "Aruba_f3a826101b9a25f3573cb0f1a7505587de8c65c3": 1, - "Honduras_5aa588714ab4cbfd615d238fd9778c3a14ca4ba8": 1, - "Bermuda_027ed37f00fb38adb089a91f2d52c7b931949168": 1, - "Anguilla_5722849f2ccb586368a07473e71e8df1bae7a221": 1, - "Curac\u0327ao_9461dd94ebb7643ea4fbbed9dc781497e05a6e7e": 1, - "Panama_1e36b31e788231fba03577144de1d23b04a5d324": 1, - "United States_768685ca582abd0af2fbb57ca37752aa98c9372b": 1, - "Peru_36c57243155b80cf350c59764354b20dde157333": 1, - "Nicaragua_4812648c8a890c1818305642f6a01fa134b34401": 1, - "Cuba_c484b137e2d18229e4e7e677f1f8cfce6a0ab819": 1, - "United States Minor Outlying Islands_ede34a33eb69cc5980a8c2353752463771b7060e": 1, - "Guadeloupe_f0f524ff3b9cf7a30a94a59841b9c0d2fd858b56": 1, - "British Virgin Islands_1fc1b5b29cefd14d32c7e252ca38633927f0c3ee": 1, - "Dominican Republic_8a4bf12e17b2be590b12721ef8d5d0248698b5b6": 1, - "Colombia_2f737399606486655401cb27b066f4658424766d": 1, - "Saint Martin_854ccc29cc22ef957d1e5ce961f30c1e92677d9c": 1, - "Antigua and Barbuda_530670dca74039e859436ac4734296b861556efd": 1, - "Belize_42ab0c94a1e3bd6175a15dba215ccf5f10e861e5": 1, - "Ecuador_09f199d25132204e99bfb1a89916de24494f19bb": 1, - "Dominica_bc1cd4f07d828493332f9c95c491d6fb338afd40": 1, - "Greenland_1ba0cfa550295f8b2c5fa44b8236639adc825cf9": 1, - "Mexico_41937b20fbe8c71d9c6c3346aff43c001aa25e33": 1, - "El Salvador_259a6e935b848823a0c0b76aced2803060b4bd0c": 1, - "Guyana_bc88a24030a15bde613c8749ed93d30e0a81fcc2": 1, - "Sint Maarten_ad7c3cc73f9cd1c049d3208aac984c49953b94b2": 1, - "Guatemala_11760e1aac4396e10d315e93ad3df3e99204dc5e": 1, - "Grenada_dbf2a2da6458b242407285e7f1483aeb6cfee9fd": 1, - "Martinique_66f1a98a952d50fc3164cde58265772e2ab821c2": 1, - "Bolivia_a001af75ee89582f31cb4db6d3dd0b4766c80050": 1, - "Barbados_93409af2f208e7545f0f26996e048113edd88652": 1, - "Venezuela_9d4ac43d5e24e3d01e448ce01a277ba5971e60f6": 1, - "Canada_cd6a7b8768528485a0dbcd459185091e80dc28ad": 1, - "Saint Barthe\u0301lemy_f48e864508243ec01f6ac7ff781f13cd5bde0a0f": 1, - "Cayman Islands_e9e21c35ab345ef790305554368ebf78279482f2": 1, - "Brazil_37497aa5a2272c49714aee1b07e8edf973a95f59": 1 - }, - "Africa": { - "Guinea_b47b54fd3a460f43ebbcabe5b10c189176f25f25": 1, - "Gabon_a06dcd71328fd2b44fa7f84012096e8f318e4b48": 1, - "Swaziland_c985df0809c3183c074ef84ac9e902d70ff81992": 1, - "Niger_6687e1896dd857dc1587782149127ff073a95696": 1, - "Lesotho_93b12bf57f18c13c9ad2f55e33a8e3fd786fc394": 1, - "Liberia_1ed5dd9d833f675b7509886681e2164d842f8dad": 1, - "Libya_55949d4c16632f1c275d7684a379b8f1717b3904": 1, - "Ethiopia_3d91f7631ba813c13a08a208f1255b2e96fb03d0": 1, - "Democratic Republic of the Congo_f39d28021812d3e91f4c64c9ac055a20d5bcfd30": 1, - "Morocco_32e087f099be121c1211285a1700ff83542193f7": 1, - "Cameroon_73a7ddd505f2fc2cead1522e54a794328f228c44": 1, - "Cape Verde_8e40809e474f3e0705ddef056618ce3e5043522a": 1, - "Botswana_180c89bf50c8b29deb45e49d9dac62fcd5c8bedb": 1, - "Seychelles_3d2d7b1c3400e9222f97a999decf652a6c34bdc5": 1, - "Co\u0302te d'Ivoire_b2182992301690448db9754c5493729921c28056": 1, - "Madagascar_f92bcb6a06d2ec7c0af7c8a338f131bf887c64a0": 1, - "Re\u0301union_8e7e261a1ff0a9bbf184ec58b7db9d014d425e65": 1, - "British Indian Ocean Territory_9bb835f4f91b31fae3286529520eecae620d718c": 1, - "Mayotte_dcc64425eed0e625072cc41ac60a88d4b3d50086": 1, - "Egypt_1c39abf68e93e438ae5dca946e2d6a986cccd3a9": 1, - "Sa\u0303o Tome\u0301 and Pri\u0301ncipe_655191a80835f37342e045bd71b73188430d3b39": 1, - "Central African Republic_dba86789b53e54df0477a589a57298893cd87502": 1, - "South Sudan_6981f4026cf83935b630141d375a9455fc18acd7": 1, - "Republic of the Congo_b78ae043a4412720001cad4a83c176d09da0437f": 1, - "Malawi_0ce65b26dcd3c08e1b329d9efbb6bbbced426f31": 1, - "Eritrea_18740af5bcec9573bd3c059e0fc6570353097aab": 1, - "Sudan_1193ba31f109ecfdfab76f13cc2b5e44479d5603": 1, - "Algeria_bd6acc8626d118aea60331ce33bf000c9d7d1cee": 1, - "Zimbabwe_5922f5ceeff38bcfdb993efd6cfd5c472f827fa9": 1, - "Ghana_317dbac90743c3e5e82b2ddf122cd076a2226a92": 1, - "Nigeria_9742d008c420ec9d44b1794c03b9701c477f93ff": 1, - "South Africa_35fda17ff05f63e9061208c2dd2aaaf98790e921": 1, - "Burkina Faso_e71b56d92347386fa76239ce33f67aaa2de52207": 1, - "Gambia_7c39974f44b2b12933c66a9eba3fe33c8d0805b6": 1, - "Saint Helena_03bdca149f934311777f23c15054c37a49e3289b": 1, - "Somalia_4dfdf195ecb76a3fa83788deca6fd3b289dd568f": 1, - "Equatorial Guinea_80eaf3087b0b041473c6c223f1e09197b649362e": 1, - "Tunisia_edf404d0db32652d41a29b428a804405e1d73a9e": 1, - "Uganda_e92904bce8026b3c1f8828b0ce882e6b081c7fb6": 1, - "Namibia_bfe79debcedda1c1e11f7836c0fc13ad22dffca7": 1, - "Kenya_a84f56f2e6a77ecb4b2f89344446dd3ff91b87c4": 1, - "Mozambique_a40a9be00a9ae956acd67011c0d96758916f40ea": 1, - "Mauritius_26160d23fb07cf8d5dae186eba322e9fc8e27bb4": 1, - "Senegal_d8973b8edfe5e3211044e39d5452523b5d69cba9": 1, - "Togo_30949d5f4a69766caa7abe5e6fd9993090b1b6b3": 1, - "Guinea-Bissau_b07cb9a2b24832a8197cd3dfa67d1d6adfb0cfbb": 1, - "Western Sahara_fab1a52391201253134c8cc0b956613cad675611": 1, - "Djibouti_60a8b0c6de6f6abe5999959a5c7352750116fb9c": 1, - "Mali_daa6a489dbca7a13c480aaa1d0c344957590fdb7": 1, - "Zambia_dcf25ed56c9566181e0f2d48d8854c04c4ca6b37": 1, - "Rwanda_7266a1da7e3a6739b245ddfe74b0b682f7da63f8": 1, - "Burundi_4617585b8749a71bbb21237cab6c2dc9cbe3b86f": 1, - "Benin_373616e39fb47d9a1a4e87dfd4ea968037435f14": 1, - "Chad_6c6b1b2b3ecc0e6900000dabf4faae6f8df5ffd1": 1, - "Angola_a42522a0cdd6e41a1379e6c95d08a9c46a17249c": 1, - "Comoros_e5bb59a2731998cae2070f6fd4f2e075fd61146f": 1, - "Tanzania_7e380be8dc28d72571144716e95e598c986bc4d6": 1, - "Sierra Leone_50df2d658581499ea96e34b53f6280fd7754bd1f": 1, - "Mauritania_85fa355bdb4f46fa53a20a441623d53d686d4036": 1 - }, - "Asia": { - "Armenia_5f4599daa3415f788c1afc3db145f01b4bd2b438": 1, - "Tajikistan_279c771ae60c33fc34bde4627bb56ee8eecca33a": 1, - "Bahrain_3ae11c725c30009d4d3418bc6b30789feed78322": 1, - "Lebanon_5caa7f811b5aff6eb9993f309c4c045785ee67ec": 1, - "Malaysia_ff3ea3bec182358766650a6fd2872d9221f7e6cc": 1, - "Timor-Leste_2d79f6dbae283f57f4092d5370a518c3ef5caec1": 1, - "Iraq_1aed9ecc6e1be8eaaaddc5ffb6ba3ed84a8b1ae6": 1, - "North Korea_765b46e79beff128df05b121d344cc7ad23dbac5": 1, - "Laos_7998be8d446d668c99abca446cb3bd79fce08d2b": 1, - "Nepal_0e1d589f58b71e2b7d5d8068156d160146820407": 1, - "Japan_fcf29f6cad3232704b33e962ef5194fad3b6817b": 1, - "Saudi Arabia_6f49c31192f5681e1053a1d699956998c5d7e97d": 1, - "Yemen_ac33d641f4c9d9de5b29bb95e7f909066cfca512": 1, - "Oman_c14c36dffca61b07410f5e3630ba5d99c60ab5d5": 1, - "Afghanistan_c69153687791fb52c12ce7cca2f4d03a65d9abf8": 1, - "Myanmar_928b7c48a60ad93b81bc3bee9d274c5f2aed9ad3": 1, - "Uzbekistan_db800e86e9f303e72e1102efb21d459512902d37": 1, - "Israel_4c197dfd67f1ed79d11a8b0218cc368bfcce6ccb": 1, - "India_967ce367d89dccc133d71049f1197d29561b3726": 1, - "Brunei_130e4a34f1b807a8f3bd24b204c06ec4de4010da": 1, - "Azerbaijan_213598a7e92217bee3a758f3c69aea09ed940c0e": 1, - "Bhutan_bb2254a806f43df24753ca390143e2ca8c1e4e80": 1, - "Qatar_83ef3e6cee83cb88a39814638a94fa1ad33e65a5": 1, - "United Arab Emirates_d115b8d5acb8386b8012aaf4cbb3812cacd97c8e": 1, - "Bangladesh_fa6c3752cd00f7f1277fd7e5604ab8d2edaf26b8": 1, - "Kyrgyzstan_c78791a3f0e109d34ba4eac2afa35ec011439ae3": 1, - "Palestine_05a15c18a2038b35edcdb52e92f002c65d4fcf32": 1, - "Syria_3ed104337eb1dfb750107978743cbf25fbe2c2b5": 1, - "Thailand_a2b7c120c93a01e67dcd4d984d2a781cde2c46df": 1, - "Cambodia_314ccd964ef6a8e68ac5f9bb89f751a1c2196c56": 1, - "Turkmenistan_1f8dc1a6076e90fb31efa48a19e2d220b647c81f": 1, - "Sri Lanka_197042c4de91dae8aeafa9a52dc8c3a59aa41dc0": 1, - "Cyprus_852addab901cbc5699d190285a009d7a7035fb57": 1, - "China_d2eaf2aa1512d6596e0a5bae633537c6b8e779a3": 1, - "Mongolia_f54da380bade5d43098fd1f338cb8257523c508b": 1, - "Kazakhstan_2f36b6bb1852c24392fb3ee9a2879da24eb0750d": 1, - "Iran_889224e3fca24a6ab17d01fe47a45bc82244e938": 1, - "South Korea_04653e650280742f94a5a74ad6530ee09c2dc2be": 1, - "Vietnam_681101d8f9fa4e5e1fbe8ac5a2c05504c6c4875a": 1, - "Indonesia_35536a41b209715d9e3ad440431fef2672f20bbe": 1, - "Singapore_20c0b7bdab70ca2cc9c844a0d74a3af0bbf41c3e": 1, - "Kuwait_93295b0c76b900da760a8e0f2e9a29a1ba4b0f4c": 1, - "Georgia_9113c6c0c1f9cb53e3543b53136ba30c51018373": 1, - "Macao_918c53a2d6129e9e4f42191b60fa11886bd9fa50": 1, - "Taiwan_094d515b3608fefc6759a36412cee467437417a5": 1, - "Pakistan_82d220df17cebf5ce4897d780c354dd5e925c209": 1, - "Philippines_8067364d44f5e37baba7e13ba124e934df410e2a": 1, - "Jordan_674027e17b0ed64e76cde2005cb8e76fb4cd671a": 1, - "Turkey_d7153e6702b4ea48c7c0d01affdef0e1b39fd6dc": 1, - "Maldives_213cb204509284ff2aedeca9290b70a6da307eab": 1, - "Hong Kong_2f488ffc82ccc67a1616e39fdfc537297f1646c5": 1 - }, - "Oceania": { - "Palau_e5e0b68adfacb66979505bedf6d424070c536eb6": 1, - "Fiji_bbb7ef7f2ec9557d0895c9da1c5cddd50d15049e": 1, - "Papua New Guinea_fbaa3c3e9e1bbf9fe813f2d4c038b295ef046512": 1, - "Cook Islands_4bacfe2535ecbceb9a04a1b85c3a2e0bc53c64d6": 1, - "Christmas Island_c6dfa97a611dbc45bfdc51c69a762488964e6361": 1, - "French Polynesia_40f39cdcd3acd771d31e4269e54caeff9b3b5edf": 1, - "Pitcairn Islands_0311d743a395837f9490fa8a89013d4b047b366e": 1, - "Tokelau_a08c1ca2fca39f1ea2de01c2de009926d07afb4d": 1, - "Northern Mariana Islands_28a117f173634e3c574231e0cfb011aabfd2bba8": 1, - "Guam_f8aa3a934ee38a6c86d1b092624a9c385267d927": 1, - "Kiribati_3f57adf2c06cecd095ff83ab72787889961bbe87": 1, - "Marshall Islands_bb130626b42c3cce860334424aef9a144f8231f2": 1, - "Tuvalu_9a2248f7c4de37c13c8e2688356857b589f669b4": 1, - "American Samoa_ca0b36fec74bc61226adce2b5ce0e8ef6fdca179": 1, - "Nauru_f648c72a69c3a9b157c3603ebd4a75141a833e46": 1, - "Tonga_e8a1234b8442f09d8965e48d93a16e10fa398baa": 1, - "Australia_ceafb51e2b0783d53dd620019dff3aa66708a26f": 1, - "Wallis and Futuna_a187509aa2afa6ef9b1abbaed04d4bb5b6124ffb": 1, - "Norfolk Island_fedb20736102194ca53af14fae03929409c27b9c": 1, - "New Zealand_a2238e91907fa2436a6a50e0fd8cf97ab60ec508": 1, - "Niue_c635e30148c3a013261cd3147f9ff8c70b2f9fea": 1, - "Solomon Islands_ecae78abec522d0fa00ddb3666aa9db9baf0265f": 1, - "Samoa_f5680604ea9e0abbe3833339ee5a793c2c02551d": 1, - "Micronesia_5a7d7bc91620c2307f0ad338a4b7d7929bb8af8e": 1, - "Cocos (Keeling) Islands_d2dd2d43fef0a1aa180b01e58e8a9c32d7ac165a": 1, - "Vanuatu_d16ad2dcc6af8252a554ea5f1722abc518dfa3b1": 1, - "New Caledonia_b9967e88983d851428fa745aa72bb8b4e0bec2da": 1 - }, - "": { - "Bouvet Island_290125ef9fc28ca6df6137e0523169786f3ecfea": 1, - "Antarctica_00f33fc530d3e011ee6ab56f206622e221888971": 1, - "French Southern and Antarctic Lands_44e46ba30eabfe96165ac52eb27f1318dd7396e4": 1, - "Heard Island and McDonald Islands_130447083d0bf6b6914b952751355ad34949129b": 1 - } -} \ No newline at end of file +{"Europe":{"Isle of Man_76733f338bb21fd91ee8304d05accb9163ee6e52":1,"Montenegro_479fb34bdbf4aba90d03cca83f64ad4e912cc854":1,"Hungary_f14e46ce7d094f9326167acc499698128651be85":1,"Bosnia and Herzegovina_62ac59b7255ce5ff9fee8ac49157bdd9bc4445e2":1,"Germany_17d53e0e6a68acdf80b78d4f9d868c8736db2cec":1,"Faroe Islands_1082f89115626b9c431f2176f832b3a7cc60fb2c":1,"Monaco_35ab1c3de6dac148401654e1ebf5dd935ccefa14":1,"Romania_d6b897fd145a64fbad36ff7cb1c47a00dcbbe9b6":1,"Netherlands_fb61c8a8eba24117016597fc7618f38821b16e8f":1,"Republic of Kosovo_338e718d14c93382811119b87ed77b25aaed8a98":1,"Russia_6754fe3cd8310d20ed04b8c7b66abebcdb16d88d":1,"France_e3772ac4b4db87b4a8dbfa59ef43cd1a8ad29515":1,"Croatia_d7e0453bb4af87006533f4d77ad9546dac533db8":1,"Estonia_f0a96da3f86a334cbe8d569110d6545277973859":1,"Vatican City_136f5db5de74430b7c1e66fcd4ef0b6a70ace276":1,"Greece_4902a456caa9a4eab463ce526c9df0f6180be184":1,"Belarus_027a12c2fc8568e8b70b07ff536faf288a013670":1,"Andorra_9d3bd1fb52785a7baeb03b437e7b2ecba54ef34f":1,"Macedonia_41349e9557e537855ae0f63cc43918fa13a561d3":1,"Guernsey_180c97abdb80833b91cb32903dced0e8f8b49562":1,"San Marino_87fd3befcce67042618f49ec496c2b09a49459fa":1,"Czech Republic_1fef42909247a6c230eeef66277f2b5e0e8f274e":1,"Svalbard and Jan Mayen_ac99404d12d60aa0c46631720dd7afa750d0e909":1,"Jersey_ff4636d0caf61d70e7dfa437d80aab75e921e76c":1,"Norway_988455e67df7cd81d090ea4bacdc05f39fb7caa5":1,"Portugal_a495190bcc2ef4116725616d321016a7def5bd8f":1,"Iceland_b3c92eecf0aa1905086059d9f6d3261d8fb19657":1,"Moldova_9791bc4d7273587d61fef79841bd342381f5e321":1,"United Kingdom_9769121f10f77079b27eb08e9ffa488cbcc37ed0":1,"Switzerland_77dcd849e550afec3c83d38fcc8cbc72c058f4db":1,"Belgium_5cb4c9d828175ed3931ec52305b32f47173a8e04":1,"Latvia_c5f5bb3b350774d7cda57104c55fb6c82b7ae7d9":1,"Spain_20a8df9b760336178fca425339ec1c7e542a2463":1,"Finland_c909b138eba89ecfbd86df4c9d170ac78d4a3820":1,"Austria_593905b31972f6ffe58325abf98595caf4ebf458":1,"Liechtenstein_b0ddce0f54c916c106117e280aead4f9c0cbf1df":1,"Italy_ad79ef0f076d3a686ab9738925f4dd2c7e69d7d1":1,"Lithuania_74a788cee27d549015a0786732c662e05cdd7567":1,"Albania_79b9d273ac6d2488109d1ea43e2bdb7977bd2b28":1,"Malta_1a591a3e91fcb7a47f2c08e9e2e117f39af22078":1,"Denmark_89da124e04dfe1ad9946cd37d91a119e1d028898":1,"Luxembourg_5076721c4060feeb69bd2c3dd9bdce115d5c62f3":1,"Serbia_6d31bf00d7eddc6a617a6b16699f8ba91794e2fd":1,"Ukraine_c951ec00f123510a00d1e3d9539b11b4631d4096":1,"Slovenia_d1aa0503612aa4168939b77b59ca74532a11951a":1,"Sweden_72ddd2b619af6d6a73febf80f7fcad22495498cd":1,"A\u030aland Islands_065154080d2f7539638e616e64cfcdb36c0577a1":1,"Ireland_eb2131ece0efe78ee8bb1ae98af6099114a8df09":1,"Slovakia_b6c149c3e00467fba347629a63ed02fed098d061":1,"Poland_5ff03b7273b1808e5ba852e230991bbf07da703c":1,"Bulgaria_5c77726358c5daf98ad9cdccd0882bca0f718b88":1,"Gibraltar_e51335897c0ef4bb952693d4166902146a1bc812":1},"Americas":{"Chile_349507e41dd8c71c10c9df6d2444b5e64a285691":1,"Saint Pierre and Miquelon_70e53283edb6d53accd21aa7731dfe1f66246b84":1,"Jamaica_5eedd6a16ab862cb5d6b2e194e0bdf8b0161d89a":1,"Argentina_354bf98925838ca68611b950e2a37ebd11c21640":1,"Uruguay_66b98924b384b40aea844bf0fe399d5d3832388e":1,"Bonaire_fd71a444cd1566324317cbc5204af645857b5b72":1,"Saint Kitts and Nevis_3ad2dd829664a86f29aec0770e244c1717c79255":1,"Costa Rica_1bf429f94068620b112aca3888b58aecbc2eadee":1,"Suriname_b17fc6f05c078b63528a6d19c2abe0917e1e8a1b":1,"Saint Vincent and the Grenadines_3fa9b2dd85d217dd4f6407fbb779928701f9e184":1,"French Guiana_07090aa7d613105dad9bd80005cd91b8da1c4cb2":1,"Montserrat_35f005157cfd133875b06f037eeeff3b7494456c":1,"Trinidad and Tobago_4d206a8103d3d20ac685314dbba1c88f6cfedd85":1,"South Georgia_618cd353b39276e0dd2215fabbbbb4d21803a69c":1,"Paraguay_71a45296474d608f35d4b49f3b10384fc738e16f":1,"Bahamas_1f797564f7843d36ebe5e841e5bd39c98157e22b":1,"Saint Lucia_673b1cf863c8e2d10109731b098b19ba81aec9fb":1,"United States Virgin Islands_65f5cb3322400cfd62ba3c8bcffbd7a8d71d0f32":1,"Turks and Caicos Islands_611bc125b7ad66ad9d5103c6bfd968a775763e6c":1,"Haiti_a4842f0234d7270b757b60b6f17a1b9f4d560dad":1,"Falkland Islands_11499959510cbab21e5241a8a6099a3d545b5227":1,"Puerto Rico_fe23c52dc2441843aa073092188089600dad336c":1,"Aruba_f3a826101b9a25f3573cb0f1a7505587de8c65c3":1,"Honduras_5aa588714ab4cbfd615d238fd9778c3a14ca4ba8":1,"Bermuda_027ed37f00fb38adb089a91f2d52c7b931949168":1,"Anguilla_5722849f2ccb586368a07473e71e8df1bae7a221":1,"Curac\u0327ao_9461dd94ebb7643ea4fbbed9dc781497e05a6e7e":1,"Panama_1e36b31e788231fba03577144de1d23b04a5d324":1,"United States_768685ca582abd0af2fbb57ca37752aa98c9372b":1,"Peru_36c57243155b80cf350c59764354b20dde157333":1,"Nicaragua_4812648c8a890c1818305642f6a01fa134b34401":1,"Cuba_c484b137e2d18229e4e7e677f1f8cfce6a0ab819":1,"United States Minor Outlying Islands_ede34a33eb69cc5980a8c2353752463771b7060e":1,"Guadeloupe_f0f524ff3b9cf7a30a94a59841b9c0d2fd858b56":1,"British Virgin Islands_1fc1b5b29cefd14d32c7e252ca38633927f0c3ee":1,"Dominican Republic_8a4bf12e17b2be590b12721ef8d5d0248698b5b6":1,"Colombia_2f737399606486655401cb27b066f4658424766d":1,"Saint Martin_854ccc29cc22ef957d1e5ce961f30c1e92677d9c":1,"Antigua and Barbuda_530670dca74039e859436ac4734296b861556efd":1,"Belize_42ab0c94a1e3bd6175a15dba215ccf5f10e861e5":1,"Ecuador_09f199d25132204e99bfb1a89916de24494f19bb":1,"Dominica_bc1cd4f07d828493332f9c95c491d6fb338afd40":1,"Greenland_1ba0cfa550295f8b2c5fa44b8236639adc825cf9":1,"Mexico_41937b20fbe8c71d9c6c3346aff43c001aa25e33":1,"El Salvador_259a6e935b848823a0c0b76aced2803060b4bd0c":1,"Guyana_bc88a24030a15bde613c8749ed93d30e0a81fcc2":1,"Sint Maarten_ad7c3cc73f9cd1c049d3208aac984c49953b94b2":1,"Guatemala_11760e1aac4396e10d315e93ad3df3e99204dc5e":1,"Grenada_dbf2a2da6458b242407285e7f1483aeb6cfee9fd":1,"Martinique_66f1a98a952d50fc3164cde58265772e2ab821c2":1,"Bolivia_a001af75ee89582f31cb4db6d3dd0b4766c80050":1,"Barbados_93409af2f208e7545f0f26996e048113edd88652":1,"Venezuela_9d4ac43d5e24e3d01e448ce01a277ba5971e60f6":1,"Canada_cd6a7b8768528485a0dbcd459185091e80dc28ad":1,"Saint Barthe\u0301lemy_f48e864508243ec01f6ac7ff781f13cd5bde0a0f":1,"Cayman Islands_e9e21c35ab345ef790305554368ebf78279482f2":1,"Brazil_37497aa5a2272c49714aee1b07e8edf973a95f59":1},"Africa":{"Guinea_b47b54fd3a460f43ebbcabe5b10c189176f25f25":1,"Gabon_a06dcd71328fd2b44fa7f84012096e8f318e4b48":1,"Swaziland_c985df0809c3183c074ef84ac9e902d70ff81992":1,"Niger_6687e1896dd857dc1587782149127ff073a95696":1,"Lesotho_93b12bf57f18c13c9ad2f55e33a8e3fd786fc394":1,"Liberia_1ed5dd9d833f675b7509886681e2164d842f8dad":1,"Libya_55949d4c16632f1c275d7684a379b8f1717b3904":1,"Ethiopia_3d91f7631ba813c13a08a208f1255b2e96fb03d0":1,"Democratic Republic of the Congo_f39d28021812d3e91f4c64c9ac055a20d5bcfd30":1,"Morocco_32e087f099be121c1211285a1700ff83542193f7":1,"Cameroon_73a7ddd505f2fc2cead1522e54a794328f228c44":1,"Cape Verde_8e40809e474f3e0705ddef056618ce3e5043522a":1,"Botswana_180c89bf50c8b29deb45e49d9dac62fcd5c8bedb":1,"Seychelles_3d2d7b1c3400e9222f97a999decf652a6c34bdc5":1,"Co\u0302te d'Ivoire_b2182992301690448db9754c5493729921c28056":1,"Madagascar_f92bcb6a06d2ec7c0af7c8a338f131bf887c64a0":1,"Re\u0301union_8e7e261a1ff0a9bbf184ec58b7db9d014d425e65":1,"British Indian Ocean Territory_9bb835f4f91b31fae3286529520eecae620d718c":1,"Mayotte_dcc64425eed0e625072cc41ac60a88d4b3d50086":1,"Egypt_1c39abf68e93e438ae5dca946e2d6a986cccd3a9":1,"Sa\u0303o Tome\u0301 and Pri\u0301ncipe_655191a80835f37342e045bd71b73188430d3b39":1,"Central African Republic_dba86789b53e54df0477a589a57298893cd87502":1,"South Sudan_6981f4026cf83935b630141d375a9455fc18acd7":1,"Republic of the Congo_b78ae043a4412720001cad4a83c176d09da0437f":1,"Malawi_0ce65b26dcd3c08e1b329d9efbb6bbbced426f31":1,"Eritrea_18740af5bcec9573bd3c059e0fc6570353097aab":1,"Sudan_1193ba31f109ecfdfab76f13cc2b5e44479d5603":1,"Algeria_bd6acc8626d118aea60331ce33bf000c9d7d1cee":1,"Zimbabwe_5922f5ceeff38bcfdb993efd6cfd5c472f827fa9":1,"Ghana_317dbac90743c3e5e82b2ddf122cd076a2226a92":1,"Nigeria_9742d008c420ec9d44b1794c03b9701c477f93ff":1,"South Africa_35fda17ff05f63e9061208c2dd2aaaf98790e921":1,"Burkina Faso_e71b56d92347386fa76239ce33f67aaa2de52207":1,"Gambia_7c39974f44b2b12933c66a9eba3fe33c8d0805b6":1,"Saint Helena_03bdca149f934311777f23c15054c37a49e3289b":1,"Somalia_4dfdf195ecb76a3fa83788deca6fd3b289dd568f":1,"Equatorial Guinea_80eaf3087b0b041473c6c223f1e09197b649362e":1,"Tunisia_edf404d0db32652d41a29b428a804405e1d73a9e":1,"Uganda_e92904bce8026b3c1f8828b0ce882e6b081c7fb6":1,"Namibia_bfe79debcedda1c1e11f7836c0fc13ad22dffca7":1,"Kenya_a84f56f2e6a77ecb4b2f89344446dd3ff91b87c4":1,"Mozambique_a40a9be00a9ae956acd67011c0d96758916f40ea":1,"Mauritius_26160d23fb07cf8d5dae186eba322e9fc8e27bb4":1,"Senegal_d8973b8edfe5e3211044e39d5452523b5d69cba9":1,"Togo_30949d5f4a69766caa7abe5e6fd9993090b1b6b3":1,"Guinea-Bissau_b07cb9a2b24832a8197cd3dfa67d1d6adfb0cfbb":1,"Western Sahara_fab1a52391201253134c8cc0b956613cad675611":1,"Djibouti_60a8b0c6de6f6abe5999959a5c7352750116fb9c":1,"Mali_daa6a489dbca7a13c480aaa1d0c344957590fdb7":1,"Zambia_dcf25ed56c9566181e0f2d48d8854c04c4ca6b37":1,"Rwanda_7266a1da7e3a6739b245ddfe74b0b682f7da63f8":1,"Burundi_4617585b8749a71bbb21237cab6c2dc9cbe3b86f":1,"Benin_373616e39fb47d9a1a4e87dfd4ea968037435f14":1,"Chad_6c6b1b2b3ecc0e6900000dabf4faae6f8df5ffd1":1,"Angola_a42522a0cdd6e41a1379e6c95d08a9c46a17249c":1,"Comoros_e5bb59a2731998cae2070f6fd4f2e075fd61146f":1,"Tanzania_7e380be8dc28d72571144716e95e598c986bc4d6":1,"Sierra Leone_50df2d658581499ea96e34b53f6280fd7754bd1f":1,"Mauritania_85fa355bdb4f46fa53a20a441623d53d686d4036":1},"Asia":{"Armenia_5f4599daa3415f788c1afc3db145f01b4bd2b438":1,"Tajikistan_279c771ae60c33fc34bde4627bb56ee8eecca33a":1,"Bahrain_3ae11c725c30009d4d3418bc6b30789feed78322":1,"Lebanon_5caa7f811b5aff6eb9993f309c4c045785ee67ec":1,"Malaysia_ff3ea3bec182358766650a6fd2872d9221f7e6cc":1,"Timor-Leste_2d79f6dbae283f57f4092d5370a518c3ef5caec1":1,"Iraq_1aed9ecc6e1be8eaaaddc5ffb6ba3ed84a8b1ae6":1,"North Korea_765b46e79beff128df05b121d344cc7ad23dbac5":1,"Laos_7998be8d446d668c99abca446cb3bd79fce08d2b":1,"Nepal_0e1d589f58b71e2b7d5d8068156d160146820407":1,"Japan_fcf29f6cad3232704b33e962ef5194fad3b6817b":1,"Saudi Arabia_6f49c31192f5681e1053a1d699956998c5d7e97d":1,"Yemen_ac33d641f4c9d9de5b29bb95e7f909066cfca512":1,"Oman_c14c36dffca61b07410f5e3630ba5d99c60ab5d5":1,"Afghanistan_c69153687791fb52c12ce7cca2f4d03a65d9abf8":1,"Myanmar_928b7c48a60ad93b81bc3bee9d274c5f2aed9ad3":1,"Uzbekistan_db800e86e9f303e72e1102efb21d459512902d37":1,"Israel_4c197dfd67f1ed79d11a8b0218cc368bfcce6ccb":1,"India_967ce367d89dccc133d71049f1197d29561b3726":1,"Brunei_130e4a34f1b807a8f3bd24b204c06ec4de4010da":1,"Azerbaijan_213598a7e92217bee3a758f3c69aea09ed940c0e":1,"Bhutan_bb2254a806f43df24753ca390143e2ca8c1e4e80":1,"Qatar_83ef3e6cee83cb88a39814638a94fa1ad33e65a5":1,"United Arab Emirates_d115b8d5acb8386b8012aaf4cbb3812cacd97c8e":1,"Bangladesh_fa6c3752cd00f7f1277fd7e5604ab8d2edaf26b8":1,"Kyrgyzstan_c78791a3f0e109d34ba4eac2afa35ec011439ae3":1,"Palestine_05a15c18a2038b35edcdb52e92f002c65d4fcf32":1,"Syria_3ed104337eb1dfb750107978743cbf25fbe2c2b5":1,"Thailand_a2b7c120c93a01e67dcd4d984d2a781cde2c46df":1,"Cambodia_314ccd964ef6a8e68ac5f9bb89f751a1c2196c56":1,"Turkmenistan_1f8dc1a6076e90fb31efa48a19e2d220b647c81f":1,"Sri Lanka_197042c4de91dae8aeafa9a52dc8c3a59aa41dc0":1,"Cyprus_852addab901cbc5699d190285a009d7a7035fb57":1,"China_d2eaf2aa1512d6596e0a5bae633537c6b8e779a3":1,"Mongolia_f54da380bade5d43098fd1f338cb8257523c508b":1,"Kazakhstan_2f36b6bb1852c24392fb3ee9a2879da24eb0750d":1,"Iran_889224e3fca24a6ab17d01fe47a45bc82244e938":1,"South Korea_04653e650280742f94a5a74ad6530ee09c2dc2be":1,"Vietnam_681101d8f9fa4e5e1fbe8ac5a2c05504c6c4875a":1,"Indonesia_35536a41b209715d9e3ad440431fef2672f20bbe":1,"Singapore_20c0b7bdab70ca2cc9c844a0d74a3af0bbf41c3e":1,"Kuwait_93295b0c76b900da760a8e0f2e9a29a1ba4b0f4c":1,"Georgia_9113c6c0c1f9cb53e3543b53136ba30c51018373":1,"Macao_918c53a2d6129e9e4f42191b60fa11886bd9fa50":1,"Taiwan_094d515b3608fefc6759a36412cee467437417a5":1,"Pakistan_82d220df17cebf5ce4897d780c354dd5e925c209":1,"Philippines_8067364d44f5e37baba7e13ba124e934df410e2a":1,"Jordan_674027e17b0ed64e76cde2005cb8e76fb4cd671a":1,"Turkey_d7153e6702b4ea48c7c0d01affdef0e1b39fd6dc":1,"Maldives_213cb204509284ff2aedeca9290b70a6da307eab":1,"Hong Kong_2f488ffc82ccc67a1616e39fdfc537297f1646c5":1},"Oceania":{"Palau_e5e0b68adfacb66979505bedf6d424070c536eb6":1,"Fiji_bbb7ef7f2ec9557d0895c9da1c5cddd50d15049e":1,"Papua New Guinea_fbaa3c3e9e1bbf9fe813f2d4c038b295ef046512":1,"Cook Islands_4bacfe2535ecbceb9a04a1b85c3a2e0bc53c64d6":1,"Christmas Island_c6dfa97a611dbc45bfdc51c69a762488964e6361":1,"French Polynesia_40f39cdcd3acd771d31e4269e54caeff9b3b5edf":1,"Pitcairn Islands_0311d743a395837f9490fa8a89013d4b047b366e":1,"Tokelau_a08c1ca2fca39f1ea2de01c2de009926d07afb4d":1,"Northern Mariana Islands_28a117f173634e3c574231e0cfb011aabfd2bba8":1,"Guam_f8aa3a934ee38a6c86d1b092624a9c385267d927":1,"Kiribati_3f57adf2c06cecd095ff83ab72787889961bbe87":1,"Marshall Islands_bb130626b42c3cce860334424aef9a144f8231f2":1,"Tuvalu_9a2248f7c4de37c13c8e2688356857b589f669b4":1,"American Samoa_ca0b36fec74bc61226adce2b5ce0e8ef6fdca179":1,"Nauru_f648c72a69c3a9b157c3603ebd4a75141a833e46":1,"Tonga_e8a1234b8442f09d8965e48d93a16e10fa398baa":1,"Australia_ceafb51e2b0783d53dd620019dff3aa66708a26f":1,"Wallis and Futuna_a187509aa2afa6ef9b1abbaed04d4bb5b6124ffb":1,"Norfolk Island_fedb20736102194ca53af14fae03929409c27b9c":1,"New Zealand_a2238e91907fa2436a6a50e0fd8cf97ab60ec508":1,"Niue_c635e30148c3a013261cd3147f9ff8c70b2f9fea":1,"Solomon Islands_ecae78abec522d0fa00ddb3666aa9db9baf0265f":1,"Samoa_f5680604ea9e0abbe3833339ee5a793c2c02551d":1,"Micronesia_5a7d7bc91620c2307f0ad338a4b7d7929bb8af8e":1,"Cocos (Keeling) Islands_d2dd2d43fef0a1aa180b01e58e8a9c32d7ac165a":1,"Vanuatu_d16ad2dcc6af8252a554ea5f1722abc518dfa3b1":1,"New Caledonia_b9967e88983d851428fa745aa72bb8b4e0bec2da":1},"":{"Bouvet Island_290125ef9fc28ca6df6137e0523169786f3ecfea":1,"Antarctica_00f33fc530d3e011ee6ab56f206622e221888971":1,"French Southern and Antarctic Lands_44e46ba30eabfe96165ac52eb27f1318dd7396e4":1,"Heard Island and McDonald Islands_130447083d0bf6b6914b952751355ad34949129b":1}} \ No newline at end of file From 15ce9fed9a6eb8d8255d0f77b21b21d5c344d46c Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Sun, 1 Dec 2019 16:50:39 +0100 Subject: [PATCH 05/13] refactor: simplify IndexInterface by removing add and remove functions as only update is required --- src/JamesMoss/Flywheel/Index/HashIndex.php | 18 +++++++--- .../Flywheel/Index/IndexInterface.php | 20 ----------- src/JamesMoss/Flywheel/Index/StoredIndex.php | 36 ------------------- src/JamesMoss/Flywheel/Repository.php | 4 +-- .../Flywheel/Index/HashIndexTest.php | 8 ++--- 5 files changed, 20 insertions(+), 66 deletions(-) diff --git a/src/JamesMoss/Flywheel/Index/HashIndex.php b/src/JamesMoss/Flywheel/Index/HashIndex.php index 9f73435..92c2ae3 100644 --- a/src/JamesMoss/Flywheel/Index/HashIndex.php +++ b/src/JamesMoss/Flywheel/Index/HashIndex.php @@ -38,7 +38,10 @@ protected function getEntries($value, $operator) } /** - * @inheritdoc + * Adds an entry in the index + * + * @param string $id + * @param string $value */ protected function addEntry($id, $value) { @@ -49,7 +52,10 @@ protected function addEntry($id, $value) } /** - * @inheritdoc + * Removes an entry from the index + * + * @param string $id + * @param string $value */ protected function removeEntry($id, $value) { @@ -67,7 +73,11 @@ protected function removeEntry($id, $value) */ protected function updateEntry($id, $new, $old) { - $this->removeEntry($id, $old); - $this->addEntry($id, $new); + if (!empty($new)) { + $this->addEntry($id, $new); + } + if (!empty($old)) { + $this->removeEntry($id, $old); + } } } diff --git a/src/JamesMoss/Flywheel/Index/IndexInterface.php b/src/JamesMoss/Flywheel/Index/IndexInterface.php index 7481d78..18c7a46 100644 --- a/src/JamesMoss/Flywheel/Index/IndexInterface.php +++ b/src/JamesMoss/Flywheel/Index/IndexInterface.php @@ -23,26 +23,6 @@ public function isOperatorCompatible($operator); */ public function get($value, $operator); - /** - * Add a document to the index. - * - * @param string $id the id of this document. - * @param mixed $value the value of this document for the indexed field. - * - * @return void - */ - public function add($id, $value); - - /** - * Remove a document from the index. - * - * @param string $id the id of this document. - * @param mixed $value the value of this document for the indexed field. - * - * @return void - */ - public function remove($id, $value); - /** * Update a document in the index. * diff --git a/src/JamesMoss/Flywheel/Index/StoredIndex.php b/src/JamesMoss/Flywheel/Index/StoredIndex.php index e82312f..ceee271 100644 --- a/src/JamesMoss/Flywheel/Index/StoredIndex.php +++ b/src/JamesMoss/Flywheel/Index/StoredIndex.php @@ -56,26 +56,6 @@ public function get($value, $operator) return $this->getEntries($value, $operator); } - /** - * @inheritdoc - */ - public function add($id, $value) - { - $this->needsData(); - $this->addEntry($id, $value); - $this->flush(); - } - - /** - * @inheritdoc - */ - public function remove($id, $value) - { - $this->needsData(); - $this->removeEntry($id, $value); - $this->flush(); - } - /** * @inheritdoc */ @@ -150,22 +130,6 @@ protected function flush() */ abstract protected function getEntries($value, $operator); - /** - * Adds an entry in the index - * - * @param string $id - * @param string $value - */ - abstract protected function addEntry($id, $value); - - /** - * Removes an entry from the index - * - * @param string $id - * @param string $value - */ - abstract protected function removeEntry($id, $value); - /** * Removes an entry from the index * diff --git a/src/JamesMoss/Flywheel/Repository.php b/src/JamesMoss/Flywheel/Repository.php index 5982b1e..c1c277c 100644 --- a/src/JamesMoss/Flywheel/Repository.php +++ b/src/JamesMoss/Flywheel/Repository.php @@ -218,9 +218,9 @@ public function store(DocumentInterface $document) $setPrev = $previous ? isset($previous->$field) : false; $setActu = isset($document->$field); if (!$setPrev && $setActu) { - $index->add($document->getId(), $document->$field); + $index->update($document->getId(), $document->$field, null); } elseif ($setPrev && !$setActu) { - $index->remove($document->getId(), $previous->$field); + $index->update($document->getId(), null, $previous->$field); } elseif ($setPrev && $setActu) { $index->update($document->getId(), $document->$field, $previous->$field); } diff --git a/test/JamesMoss/Flywheel/Index/HashIndexTest.php b/test/JamesMoss/Flywheel/Index/HashIndexTest.php index 7fbcc35..10b437c 100644 --- a/test/JamesMoss/Flywheel/Index/HashIndexTest.php +++ b/test/JamesMoss/Flywheel/Index/HashIndexTest.php @@ -45,23 +45,23 @@ protected function tearDown() public function testAddEntry() { $id = 'testdoc123'; - $this->index->add($id, '123'); + $this->index->update($id, '123', null); $this->assertEquals(array($id), $this->index->get('123', '==')); } public function testRemoveEntry() { $id = 'testdoc123'; - $this->index->add($id, '123'); + $this->index->update($id, '123', null); $this->assertEquals(array($id), $this->index->get('123', '==')); - $this->index->remove($id, '123'); + $this->index->update($id, null, '123'); $this->assertEquals(array(), $this->index->get('123', '==')); } public function testUpdateEntry() { $id = 'testdoc123'; - $this->index->add($id, '123'); + $this->index->update($id, '123', null); $this->assertEquals(array($id), $this->index->get('123', '==')); $this->index->update($id, '456', '123'); $this->assertEquals(array(), $this->index->get('123', '==')); From 2e8d85db8a64b66580be596cd70d71dc1312e3d6 Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Sun, 1 Dec 2019 16:54:33 +0100 Subject: [PATCH 06/13] fix: multidimentional key indexes bugs update and delte functions of the repo were not tested and didn't work this is now fixed --- src/JamesMoss/Flywheel/Repository.php | 58 +++++++++++++------ .../Flywheel/Index/HashIndexTest.php | 19 +++--- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/src/JamesMoss/Flywheel/Repository.php b/src/JamesMoss/Flywheel/Repository.php index c1c277c..488efd8 100644 --- a/src/JamesMoss/Flywheel/Repository.php +++ b/src/JamesMoss/Flywheel/Repository.php @@ -116,7 +116,10 @@ public function findAll() foreach ($files as $file) { $fp = fopen($file, 'r'); - $contents = fread($fp, filesize($file)); + $contents = null; + if(($filesize = filesize($file)) > 0) { + $contents = fread($fp, $filesize); + } fclose($fp); $data = $this->formatter->decode($contents); @@ -146,7 +149,10 @@ public function findById($id) } $fp = fopen($path, 'r'); - $contents = fread($fp, filesize($path)); + $contents = null; + if(($filesize = filesize($path)) > 0) { + $contents = fread($fp, $filesize); + } fclose($fp); $data = $this->formatter->decode($contents); @@ -179,7 +185,10 @@ public function findByIds($ids) return false; } $fp = fopen($path, 'r'); - $contents = fread($fp, filesize($path)); + $contents = null; + if(($filesize = filesize($path)) > 0) { + $contents = fread($fp, $filesize); + } fclose($fp); $data = $this->formatter->decode($contents); @@ -215,14 +224,16 @@ public function store(DocumentInterface $document) } $previous = $this->findById($id); foreach ($this->indexes as $field => $index) { - $setPrev = $previous ? isset($previous->$field) : false; - $setActu = isset($document->$field); - if (!$setPrev && $setActu) { - $index->update($document->getId(), $document->$field, null); - } elseif ($setPrev && !$setActu) { - $index->update($document->getId(), null, $previous->$field); - } elseif ($setPrev && $setActu) { - $index->update($document->getId(), $document->$field, $previous->$field); + $oldFound = false; + $newFound = false; + $oldVal = $previous ? $previous->getNestedProperty($field, $oldFound) : null; + $newVal = $document->getNestedProperty($field, $newFound); + if (!$oldFound && $newFound) { + $index->update($document->getId(), $newVal, null); + } elseif ($oldFound && !$newFound) { + $index->update($document->getId(), null, $oldVal); + } elseif ($oldFound && $newFound) { + $index->update($document->getId(), $newVal, $oldVal); } } @@ -243,7 +254,7 @@ public function store(DocumentInterface $document) * * @param Document $document The document to store * - * @return bool True if stored, otherwise false + * @return string|false the id if stored, otherwise false */ public function update(DocumentInterface $document) { @@ -261,8 +272,9 @@ public function update(DocumentInterface $document) if($document->getId() !== $document->getInitialId()) { $previous = $this->findById($document->getInitialId()); foreach ($this->indexes as $field => $index) { - if(isset($previous->$field)) { - $index->remove($previous->getId(), $previous->$field); + $value = $previous->getNestedProperty($field, $found); + if ($found) { + $index->update($previous->getId(), null, $value); } } unlink($oldPath); @@ -274,14 +286,24 @@ public function update(DocumentInterface $document) /** * Delete a document from the repository using its ID. * - * @param mixed $id The ID of the document (or the document itself) to delete + * @param mixed $doc The ID of the document (or the document itself) to delete * * @return boolean True if deleted, false if not. */ - public function delete($id) + public function delete($doc) { - if ($id instanceof DocumentInterface) { - $id = $id->getId(); + if ($doc instanceof DocumentInterface) { + $id = $doc->getId(); + } else { + $id = $doc; + $doc = $this->findById($id); + } + foreach ($this->indexes as $field => $index) { + $found = false; + $value = $doc ? $doc->getNestedProperty($field, $found) : null; + if ($found) { + $index->update($id, null, $value); + } } $path = $this->getPathForDocument($id); diff --git a/test/JamesMoss/Flywheel/Index/HashIndexTest.php b/test/JamesMoss/Flywheel/Index/HashIndexTest.php index 10b437c..81bbe1e 100644 --- a/test/JamesMoss/Flywheel/Index/HashIndexTest.php +++ b/test/JamesMoss/Flywheel/Index/HashIndexTest.php @@ -161,11 +161,10 @@ public function testExistingData() $this->assertEquals(array($id), $this->index->get('123', '==')); } - public function testDeepKey() + public function testMultidimentionalKey() { - $id = 'testdoc123456'; + $id = 'testdoc456'; $doc = new Document(array( - 'col1' => '123', 'col2' => array('4', '5', '6'), )); $doc->setId($id); @@ -178,18 +177,22 @@ public function testDeepKey() ) )); - $this->assertEquals($id, $this->repo->store($doc)); - $this->assertEquals($id, $repo2->store($doc)); - // test generating index from fs + $this->assertEquals($id, $this->repo->store($doc)); $index1 = new HashIndex('col2.0', self::REPO_PATH . '.indexes', new JSON(), $this->repo); $this->assertEquals(array($id), $index1->get('4', '==')); + $this->assertTrue($this->repo->delete($doc)); // test generating index on store document + $this->assertEquals($id, $repo2->store($doc)); $index2 = $repo2->getIndexes()['col2.0']; $this->assertEquals(array($id), $index2->get('4', '==')); - - + $id = 'doc456'; + $doc->setId($id); + $this->assertEquals($id, $repo2->update($doc)); + $this->assertEquals(array($id), $index2->get('4', '==')); + $this->assertTrue($repo2->delete($id)); + $this->assertEquals(array(), $index2->get('4', '==')); } } \ No newline at end of file From a4c0d133ba458c74590acaedb2db435cae5c0b48 Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Sun, 1 Dec 2019 18:28:02 +0100 Subject: [PATCH 07/13] feat(HashIndex): working '!=' and '!==' operators these two operators are not very efficient so i'm not sure to keep them in the hashindex, but at least they are working --- src/JamesMoss/Flywheel/Index/HashIndex.php | 13 +++-- .../Flywheel/Index/HashIndexTest.php | 13 +++++ test/JamesMoss/Flywheel/QueryExecuterTest.php | 52 ++++++++++--------- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/JamesMoss/Flywheel/Index/HashIndex.php b/src/JamesMoss/Flywheel/Index/HashIndex.php index 92c2ae3..4c72587 100644 --- a/src/JamesMoss/Flywheel/Index/HashIndex.php +++ b/src/JamesMoss/Flywheel/Index/HashIndex.php @@ -27,12 +27,11 @@ protected function getEntries($value, $operator) if (!isset($this->data->$value)) { return array(); } - $ids = array_keys(get_object_vars($this->data->$value)); switch ($operator) { case '==': - case '===': return $ids; + case '===': return array_keys(get_object_vars($this->data->$value)); case '!=': - case '!==': return array_diff(get_object_vars($this->data), $ids); + case '!==': return $this->idsExcept($value); default: throw new \InvalidArgumentException('Incompatible operator `'.$operator.'`.'); } } @@ -80,4 +79,12 @@ protected function updateEntry($id, $new, $old) $this->removeEntry($id, $old); } } + + protected function idsExcept($value) { + $data = get_object_vars($this->data); + unset($data[$value]); + return array_keys(array_reduce($data, function($prev, $val) { + return array_merge($prev, get_object_vars($val)); + }, array())); + } } diff --git a/test/JamesMoss/Flywheel/Index/HashIndexTest.php b/test/JamesMoss/Flywheel/Index/HashIndexTest.php index 81bbe1e..1da0aab 100644 --- a/test/JamesMoss/Flywheel/Index/HashIndexTest.php +++ b/test/JamesMoss/Flywheel/Index/HashIndexTest.php @@ -68,6 +68,19 @@ public function testUpdateEntry() $this->assertEquals(array($id), $this->index->get('456', '==')); } + public function testGet() + { + $n = 4; + for ($i=1; $i <= $n; $i++) { + $id = "doc$i"; + $this->index->update($id, "val$i", null); + } + $this->assertEquals(array('doc1'), $this->index->get('val1', '==')); + $this->assertEquals(array('doc2'), $this->index->get('val2', '===')); + $this->assertEquals(array('doc1', 'doc2', 'doc4'), $this->index->get('val3', '!=')); + $this->assertEquals(array('doc1', 'doc2', 'doc3'), $this->index->get('val4', '!==')); + } + public function testStoreDocument() { $doc = new Document(array( diff --git a/test/JamesMoss/Flywheel/QueryExecuterTest.php b/test/JamesMoss/Flywheel/QueryExecuterTest.php index a17ccc8..dc51ce5 100644 --- a/test/JamesMoss/Flywheel/QueryExecuterTest.php +++ b/test/JamesMoss/Flywheel/QueryExecuterTest.php @@ -195,32 +195,34 @@ public function testOrderingById() public function testFindByIndex() { - $pred = $this->getPredicate() - ->where('region', '==', 'Europe'); - $options = array( - 'indexes' => array( - 'region' => '\JamesMoss\Flywheel\Index\HashIndex' - ) - ); - $n = 5; - - $qe = new QueryExecuter($this->getRepo('countries', $options), $pred, array(), array()); - $start = microtime(true); - for ($i=0; $i < $n; $i++) { - $withIndex = $qe->run(); + foreach(array('==', '===', '!=', '!==') as $operator) { + $pred = $this->getPredicate() + ->where('region', $operator, 'Europe'); + $options = array( + 'indexes' => array( + 'region' => '\JamesMoss\Flywheel\Index\HashIndex' + ) + ); + $n = 5; + + $qe = new QueryExecuter($this->getRepo('countries', $options), $pred, array(), array()); + $start = microtime(true); + for ($i=0; $i < $n; $i++) { + $withIndex = $qe->run(); + } + $timeWithIndex = microtime(true) - $start; + + $qe = new QueryExecuter($this->getRepo('countries'), $pred, array(), array()); + $start = microtime(true); + for ($i=0; $i < $n; $i++) { + $withoutIndex = $qe->run(); + } + $timeWithoutIndex = microtime(true) - $start; + + $this->assertSameSize($withoutIndex, $withIndex); + $this->assertEqualsUnordered(get_object_vars($withoutIndex), get_object_vars($withIndex)); + $this->assertLessThan($timeWithoutIndex, $timeWithIndex); } - $timeWithIndex = microtime(true) - $start; - - $qe = new QueryExecuter($this->getRepo('countries'), $pred, array(), array()); - $start = microtime(true); - for ($i=0; $i < $n; $i++) { - $withoutIndex = $qe->run(); - } - $timeWithoutIndex = microtime(true) - $start; - - $this->assertSameSize($withoutIndex, $withIndex); - $this->assertEqualsUnordered(get_object_vars($withoutIndex), get_object_vars($withIndex)); - $this->assertLessThan($timeWithoutIndex, $timeWithIndex); } From d631398aa564d0c4d42c465189efb3e009913e37 Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Mon, 2 Dec 2019 16:35:17 +0100 Subject: [PATCH 08/13] refactor: cleaner IndexInterface - move $data initialisation into concrete StoredIndex extensions - add constructor definition to IndexInterface to make sure it is compatible with the initialisation in Repository - completely abstract filesystem from HashIndex (constructor) --- src/JamesMoss/Flywheel/Index/HashIndex.php | 16 ++++++++++ .../Flywheel/Index/IndexInterface.php | 8 +++++ src/JamesMoss/Flywheel/Index/StoredIndex.php | 30 ++++++++++++------- src/JamesMoss/Flywheel/Repository.php | 23 ++++++++++---- .../Flywheel/Index/HashIndexTest.php | 4 +-- 5 files changed, 64 insertions(+), 17 deletions(-) diff --git a/src/JamesMoss/Flywheel/Index/HashIndex.php b/src/JamesMoss/Flywheel/Index/HashIndex.php index 4c72587..145b3f4 100644 --- a/src/JamesMoss/Flywheel/Index/HashIndex.php +++ b/src/JamesMoss/Flywheel/Index/HashIndex.php @@ -2,6 +2,7 @@ namespace JamesMoss\Flywheel\Index; +use JamesMoss\Flywheel\Formatter\JSON; use JamesMoss\Flywheel\Index\StoredIndex; use stdClass; @@ -11,6 +12,13 @@ class HashIndex extends StoredIndex '==', '===', '!=', '!==' ); + /** + * @inheritdoc + */ + public function __construct($field, $repository) { + $this->construct($field, $repository); + } + /** * @inheritdoc */ @@ -19,6 +27,14 @@ public function isOperatorCompatible($operator) return in_array($operator, self::$operators); } + /** + * @inheritdoc + */ + protected function initData() + { + $this->data = new stdClass(); + } + /** * @inheritdoc */ diff --git a/src/JamesMoss/Flywheel/Index/IndexInterface.php b/src/JamesMoss/Flywheel/Index/IndexInterface.php index 18c7a46..8686934 100644 --- a/src/JamesMoss/Flywheel/Index/IndexInterface.php +++ b/src/JamesMoss/Flywheel/Index/IndexInterface.php @@ -4,6 +4,14 @@ interface IndexInterface { + /** + * Constructor + * + * @param string $field the field to index. + * @param Repository $repository the repository of this index. + */ + public function __construct($field, $repository); + /** * Checks if the given operator is compatible with this index. * diff --git a/src/JamesMoss/Flywheel/Index/StoredIndex.php b/src/JamesMoss/Flywheel/Index/StoredIndex.php index ceee271..5673f39 100644 --- a/src/JamesMoss/Flywheel/Index/StoredIndex.php +++ b/src/JamesMoss/Flywheel/Index/StoredIndex.php @@ -4,10 +4,10 @@ use JamesMoss\Flywheel\Index\IndexInterface; use JamesMoss\Flywheel\Formatter\FormatInterface; +use JamesMoss\Flywheel\Formatter\JSON; use JamesMoss\Flywheel\Predicate; use JamesMoss\Flywheel\QueryExecuter; use JamesMoss\Flywheel\Repository; -use stdClass; abstract class StoredIndex implements IndexInterface { @@ -27,21 +27,25 @@ abstract class StoredIndex implements IndexInterface protected $path; /** - * Constructor + * Protected constructor * * @param string $field the field to index. - * @param string $path the directory where to store the index file. - * @param FormatInterface $formatter the formatter used to store the data. * @param Repository $repository the repository of this index. + * @param FormatInterface $formatter the formatter used to store the data. */ - public function __construct($field, $path, $formatter, $repository) + protected function construct($field, $repository, $formatter = null) { $this->field = $field; - $this->formatter = $formatter; + $this->formatter = $formatter == null ? new JSON() : $formatter; $this->repository = $repository; - $this->path = $path . DIRECTORY_SEPARATOR . "$field." . $formatter->getFileExtension(); + $this->path = $repository->addDirectory('.indexes') . DIRECTORY_SEPARATOR . "$field." . $this->formatter->getFileExtension(); } + /** + * @inheritdoc + */ + abstract public function __construct($field, $repository); + /** * @inheritdoc */ @@ -79,7 +83,7 @@ protected function needsData() if (isset($this->data)) { return; } - $this->data = new stdClass(); + $this->initData(); if (file_exists($this->path)) { $fp = fopen($this->path, 'r'); $contents = fread($fp, filesize($this->path)); @@ -91,11 +95,10 @@ protected function needsData() $qe = new QueryExecuter($this->repository, $predicate->where($field, '=='), array(), array()); foreach ($this->repository->findAll() as $doc) { $docVal = $qe->getFieldValue($doc, $field, $found); - if (!$found) { continue; } - $this->addEntry($doc->getId(), $docVal); + $this->updateEntry($doc->getId(), $docVal, null); } $this->flush(); } @@ -120,6 +123,13 @@ protected function flush() return $result !== false; } + /** + * Init the data object + * + * @return void + */ + abstract protected function initData(); + /** * Get entries from the index * diff --git a/src/JamesMoss/Flywheel/Repository.php b/src/JamesMoss/Flywheel/Repository.php index 488efd8..a7a65c3 100644 --- a/src/JamesMoss/Flywheel/Repository.php +++ b/src/JamesMoss/Flywheel/Repository.php @@ -2,7 +2,6 @@ namespace JamesMoss\Flywheel; -use JamesMoss\Flywheel\Formatter\JSON; use JamesMoss\Flywheel\Index\IndexInterface; /** @@ -18,7 +17,6 @@ class Repository protected $formatter; protected $queryClass; protected $documentClass; - protected $indexDir; /** @var array $indexes */ protected $indexes; @@ -37,15 +35,16 @@ public function __construct($name, Config $config) $this->queryClass = $config->getOption('query_class'); $this->documentClass = $config->getOption('document_class'); $this->indexes = $config->getOption('indexes', array()); - $this->indexDir = $this->path . DIRECTORY_SEPARATOR . '.indexes'; array_walk($this->indexes, function(&$class, $field) { - $class = new $class($field, $this->indexDir, new JSON(), $this); + if (!is_subclass_of($class, '\JamesMoss\Flywheel\Index\IndexInterface')) { + throw new \RuntimeException(sprintf('`%s` does not implement IndexInterface.', $class)); + } + $class = new $class($field, $this); }); // Ensure the repo name is valid $this->validateName($this->name); $this->ensureDirectory($this->path); - $this->ensureDirectory($this->indexDir); } /** @@ -61,6 +60,20 @@ protected function ensureDirectory($path) { } } + /** + * Adds a directory in the repository. + * + * @param string $name The name of the new directory. + * + * @return string The path of the directory. + */ + public function addDirectory($name) + { + $path = $this->path . DIRECTORY_SEPARATOR . $name; + $this->ensureDirectory($path); + return $path; + } + /** * Returns the name of this repository * diff --git a/test/JamesMoss/Flywheel/Index/HashIndexTest.php b/test/JamesMoss/Flywheel/Index/HashIndexTest.php index 1da0aab..c85d327 100644 --- a/test/JamesMoss/Flywheel/Index/HashIndexTest.php +++ b/test/JamesMoss/Flywheel/Index/HashIndexTest.php @@ -33,7 +33,7 @@ protected function setUp() ) )); $this->repo = new Repository(self::REPO_NAME, $config); - $this->index = new HashIndex('col1', self::REPO_PATH . '.indexes', new JSON(), $this->repo); + $this->index = new HashIndex('col1', $this->repo); } protected function tearDown() @@ -192,7 +192,7 @@ public function testMultidimentionalKey() // test generating index from fs $this->assertEquals($id, $this->repo->store($doc)); - $index1 = new HashIndex('col2.0', self::REPO_PATH . '.indexes', new JSON(), $this->repo); + $index1 = new HashIndex('col2.0', $this->repo); $this->assertEquals(array($id), $index1->get('4', '==')); $this->assertTrue($this->repo->delete($doc)); From 7d6f20d29b3e05a42764f46933b1c8b6beb1dd27 Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Mon, 2 Dec 2019 17:44:26 +0100 Subject: [PATCH 09/13] fix(HashIndex): bad result with inconsitent data - removed operators '===' and '!==' as I don't think we can implement them with the current HashIndex data structure. (We could maybe write the type in the key to implement this) - change test accordingly - add inconsistent data test - simplify index generation from document files --- src/JamesMoss/Flywheel/Index/HashIndex.php | 12 +++--- src/JamesMoss/Flywheel/Index/StoredIndex.php | 7 +--- .../Flywheel/Index/HashIndexTest.php | 37 ++++++++++++++++++- test/JamesMoss/Flywheel/QueryExecuterTest.php | 2 +- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/JamesMoss/Flywheel/Index/HashIndex.php b/src/JamesMoss/Flywheel/Index/HashIndex.php index 145b3f4..4d8a7a0 100644 --- a/src/JamesMoss/Flywheel/Index/HashIndex.php +++ b/src/JamesMoss/Flywheel/Index/HashIndex.php @@ -9,7 +9,7 @@ class HashIndex extends StoredIndex { protected static $operators = array( - '==', '===', '!=', '!==' + '==', '!=' ); /** @@ -44,10 +44,8 @@ protected function getEntries($value, $operator) return array(); } switch ($operator) { - case '==': - case '===': return array_keys(get_object_vars($this->data->$value)); - case '!=': - case '!==': return $this->idsExcept($value); + case '==': return array_keys(get_object_vars($this->data->$value)); + case '!=': return $this->idsExcept($value); default: throw new \InvalidArgumentException('Incompatible operator `'.$operator.'`.'); } } @@ -88,10 +86,10 @@ protected function removeEntry($id, $value) */ protected function updateEntry($id, $new, $old) { - if (!empty($new)) { + if ($new !== null) { $this->addEntry($id, $new); } - if (!empty($old)) { + if ($old !== null) { $this->removeEntry($id, $old); } } diff --git a/src/JamesMoss/Flywheel/Index/StoredIndex.php b/src/JamesMoss/Flywheel/Index/StoredIndex.php index 5673f39..b74af6b 100644 --- a/src/JamesMoss/Flywheel/Index/StoredIndex.php +++ b/src/JamesMoss/Flywheel/Index/StoredIndex.php @@ -5,8 +5,6 @@ use JamesMoss\Flywheel\Index\IndexInterface; use JamesMoss\Flywheel\Formatter\FormatInterface; use JamesMoss\Flywheel\Formatter\JSON; -use JamesMoss\Flywheel\Predicate; -use JamesMoss\Flywheel\QueryExecuter; use JamesMoss\Flywheel\Repository; abstract class StoredIndex implements IndexInterface @@ -90,11 +88,8 @@ protected function needsData() fclose($fp); $this->data = $this->formatter->decode($contents); } else { - $field = $this->field; - $predicate = new Predicate(); - $qe = new QueryExecuter($this->repository, $predicate->where($field, '=='), array(), array()); foreach ($this->repository->findAll() as $doc) { - $docVal = $qe->getFieldValue($doc, $field, $found); + $docVal = $doc->getNestedProperty($this->field, $found); if (!$found) { continue; } diff --git a/test/JamesMoss/Flywheel/Index/HashIndexTest.php b/test/JamesMoss/Flywheel/Index/HashIndexTest.php index c85d327..90a7094 100644 --- a/test/JamesMoss/Flywheel/Index/HashIndexTest.php +++ b/test/JamesMoss/Flywheel/Index/HashIndexTest.php @@ -76,9 +76,7 @@ public function testGet() $this->index->update($id, "val$i", null); } $this->assertEquals(array('doc1'), $this->index->get('val1', '==')); - $this->assertEquals(array('doc2'), $this->index->get('val2', '===')); $this->assertEquals(array('doc1', 'doc2', 'doc4'), $this->index->get('val3', '!=')); - $this->assertEquals(array('doc1', 'doc2', 'doc3'), $this->index->get('val4', '!==')); } public function testStoreDocument() @@ -208,4 +206,39 @@ public function testMultidimentionalKey() $this->assertEquals(array(), $index2->get('4', '==')); } + public function testInconsitentData() + { + $doc1 = new Document(array('col1' => '1')); + $doc1->setId('doc1'); + $doc2 = new Document(array('col2' => '2')); + $doc2->setId('doc2'); + $doc3 = new Document(array('col1' => '')); + $doc3->setId('doc3'); + $doc4 = new Document(array('col2' => '4')); + $doc4->setId('doc4'); + $doc5 = new Document(array('col1' => '')); + $doc5->setId('doc5'); + + $repo2 = new Repository(self::REPO_NAME, new Config(self::REPO_DIR, array())); + $query11 = $this->repo->query()->where('col1', '==', 1)->orderBy('__id'); + $query12 = $this->repo->query()->where('col1', '!=', 1)->orderBy('__id'); + $query21 = $repo2->query()->where('col1', '==', 1)->orderBy('__id'); + $query22 = $repo2->query()->where('col1', '!=', 1)->orderBy('__id'); + + // test generating index from document files + $this->assertEquals('doc1', $repo2->store($doc1)); + $this->assertEquals('doc2', $repo2->store($doc2)); + $this->assertEquals('doc3', $repo2->store($doc3)); + $this->assertEquals($query21->execute(), $query11->execute()); + $this->assertEquals($query22->execute(), $query12->execute()); + + // test generating index on store document + $this->assertEquals('doc4', $this->repo->store($doc4)); + $this->assertEquals('doc4', $repo2->store($doc4)); + $this->assertEquals('doc5', $this->repo->store($doc5)); + $this->assertEquals('doc5', $repo2->store($doc5)); + $this->assertEquals($query21->execute(), $query11->execute()); + $this->assertEquals($query22->execute(), $query12->execute()); + } + } \ No newline at end of file diff --git a/test/JamesMoss/Flywheel/QueryExecuterTest.php b/test/JamesMoss/Flywheel/QueryExecuterTest.php index dc51ce5..659f21d 100644 --- a/test/JamesMoss/Flywheel/QueryExecuterTest.php +++ b/test/JamesMoss/Flywheel/QueryExecuterTest.php @@ -195,7 +195,7 @@ public function testOrderingById() public function testFindByIndex() { - foreach(array('==', '===', '!=', '!==') as $operator) { + foreach(array('==', '!=') as $operator) { $pred = $this->getPredicate() ->where('region', $operator, 'Europe'); $options = array( From ee3e297ee2c31597e6bd25ffa0baa2e184829648 Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Mon, 2 Dec 2019 19:36:32 +0100 Subject: [PATCH 10/13] fix(HashIndex): empty string in index use an array instead of an stdClass to store the data --- src/JamesMoss/Flywheel/Formatter/JSON.php | 2 +- src/JamesMoss/Flywheel/Index/HashIndex.php | 26 ++++++++++---------- src/JamesMoss/Flywheel/Index/StoredIndex.php | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/JamesMoss/Flywheel/Formatter/JSON.php b/src/JamesMoss/Flywheel/Formatter/JSON.php index 4fe3c16..fd1c5c8 100644 --- a/src/JamesMoss/Flywheel/Formatter/JSON.php +++ b/src/JamesMoss/Flywheel/Formatter/JSON.php @@ -23,6 +23,6 @@ public function encode(array $data) public function decode($data) { - return json_decode($data); + return json_decode($data, $this->jsonOptions&JSON_OBJECT_AS_ARRAY); } } \ No newline at end of file diff --git a/src/JamesMoss/Flywheel/Index/HashIndex.php b/src/JamesMoss/Flywheel/Index/HashIndex.php index 4d8a7a0..50095ec 100644 --- a/src/JamesMoss/Flywheel/Index/HashIndex.php +++ b/src/JamesMoss/Flywheel/Index/HashIndex.php @@ -16,7 +16,7 @@ class HashIndex extends StoredIndex * @inheritdoc */ public function __construct($field, $repository) { - $this->construct($field, $repository); + $this->construct($field, $repository, new JSON(JSON_OBJECT_AS_ARRAY)); } /** @@ -32,7 +32,7 @@ public function isOperatorCompatible($operator) */ protected function initData() { - $this->data = new stdClass(); + $this->data = array(); } /** @@ -40,11 +40,11 @@ protected function initData() */ protected function getEntries($value, $operator) { - if (!isset($this->data->$value)) { + if (!isset($this->data[$value])) { return array(); } switch ($operator) { - case '==': return array_keys(get_object_vars($this->data->$value)); + case '==': return array_keys($this->data[$value]); case '!=': return $this->idsExcept($value); default: throw new \InvalidArgumentException('Incompatible operator `'.$operator.'`.'); } @@ -58,10 +58,10 @@ protected function getEntries($value, $operator) */ protected function addEntry($id, $value) { - if (!isset($this->data->$value)) { - $this->data->$value = new stdClass(); + if (!isset($this->data[$value])) { + $this->data[$value] = array(); } - $this->data->$value->$id = 1; + $this->data[$value][$id] = 1; } /** @@ -72,12 +72,12 @@ protected function addEntry($id, $value) */ protected function removeEntry($id, $value) { - if (!isset($this->data->$value)) { + if (!isset($this->data[$value])) { return; } - unset($this->data->$value->$id); - if (count(get_object_vars($this->data->$value)) === 0) { - unset($this->data->$value); + unset($this->data[$value][$id]); + if (count($this->data[$value]) === 0) { + unset($this->data[$value]); } } @@ -95,10 +95,10 @@ protected function updateEntry($id, $new, $old) } protected function idsExcept($value) { - $data = get_object_vars($this->data); + $data = $this->data; unset($data[$value]); return array_keys(array_reduce($data, function($prev, $val) { - return array_merge($prev, get_object_vars($val)); + return array_merge($prev, $val); }, array())); } } diff --git a/src/JamesMoss/Flywheel/Index/StoredIndex.php b/src/JamesMoss/Flywheel/Index/StoredIndex.php index b74af6b..12b56f1 100644 --- a/src/JamesMoss/Flywheel/Index/StoredIndex.php +++ b/src/JamesMoss/Flywheel/Index/StoredIndex.php @@ -106,7 +106,7 @@ protected function needsData() */ protected function flush() { - $contents = $this->formatter->encode(get_object_vars($this->data)); + $contents = $this->formatter->encode($this->data); $fp = fopen($this->path, 'w'); if (!flock($fp, LOCK_EX)) { return false; From 37aaa9eb0a4814a795307d885f3362e5c2a38865 Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Mon, 2 Dec 2019 20:30:39 +0100 Subject: [PATCH 11/13] build: add php 5.3 again + fix tests for php5.3 --- .travis.yml | 2 ++ composer.json | 2 +- composer.lock | 10 ++++++---- src/JamesMoss/Flywheel/Formatter/JSON.php | 3 +++ src/JamesMoss/Flywheel/Repository.php | 5 +++-- test/JamesMoss/Flywheel/Index/HashIndexTest.php | 3 ++- test/JamesMoss/Flywheel/QueryExecuterTest.php | 16 +++++++--------- 7 files changed, 24 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 245c4eb..f1f7bf1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,8 @@ jobs: dist: trusty - php: 5.4 dist: trusty + - php: 5.3.29 + dist: precise install: - if [[ ${TRAVIS_PHP_VERSION:0:3} < "7.1" ]]; then rm composer.lock; fi diff --git a/composer.json b/composer.json index 7c9b4ad..5e1d195 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require-dev": { "mikey179/vfsstream": "^1.6", "mockery/mockery": "1.* || 0.*", - "mustangostang/spyc": "^0.6", + "mustangostang/spyc": "dev-master#eba310c", "phpunit/phpunit": "7.* || 4.*" }, "suggest": { diff --git a/composer.lock b/composer.lock index 4fb8d1d..cad2a38 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "ac5e07039672da7f040f0ff8506869f6", + "content-hash": "857529a9f504739efe6a1d43c40bdb5d", "packages": [], "packages-dev": [ { @@ -225,7 +225,7 @@ }, { "name": "mustangostang/spyc", - "version": "0.6.3", + "version": "dev-master", "source": { "type": "git", "url": "git@github.com:mustangostang/spyc.git", @@ -1540,7 +1540,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.13.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -1687,7 +1687,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "mustangostang/spyc": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/JamesMoss/Flywheel/Formatter/JSON.php b/src/JamesMoss/Flywheel/Formatter/JSON.php index fd1c5c8..89d9630 100644 --- a/src/JamesMoss/Flywheel/Formatter/JSON.php +++ b/src/JamesMoss/Flywheel/Formatter/JSON.php @@ -2,6 +2,9 @@ namespace JamesMoss\Flywheel\Formatter; +defined('JSON_OBJECT_AS_ARRAY') or define('JSON_OBJECT_AS_ARRAY', 1); +defined('JSON_PRETTY_PRINT') or define('JSON_PRETTY_PRINT', 128); + class JSON implements FormatInterface { protected $jsonOptions; diff --git a/src/JamesMoss/Flywheel/Repository.php b/src/JamesMoss/Flywheel/Repository.php index a7a65c3..bbb8936 100644 --- a/src/JamesMoss/Flywheel/Repository.php +++ b/src/JamesMoss/Flywheel/Repository.php @@ -35,11 +35,12 @@ public function __construct($name, Config $config) $this->queryClass = $config->getOption('query_class'); $this->documentClass = $config->getOption('document_class'); $this->indexes = $config->getOption('indexes', array()); - array_walk($this->indexes, function(&$class, $field) { + $self = $this; + array_walk($this->indexes, function(&$class, $field) use ($self) { if (!is_subclass_of($class, '\JamesMoss\Flywheel\Index\IndexInterface')) { throw new \RuntimeException(sprintf('`%s` does not implement IndexInterface.', $class)); } - $class = new $class($field, $this); + $class = new $class($field, $self); }); // Ensure the repo name is valid diff --git a/test/JamesMoss/Flywheel/Index/HashIndexTest.php b/test/JamesMoss/Flywheel/Index/HashIndexTest.php index 90a7094..4e25ee0 100644 --- a/test/JamesMoss/Flywheel/Index/HashIndexTest.php +++ b/test/JamesMoss/Flywheel/Index/HashIndexTest.php @@ -196,7 +196,8 @@ public function testMultidimentionalKey() // test generating index on store document $this->assertEquals($id, $repo2->store($doc)); - $index2 = $repo2->getIndexes()['col2.0']; + $indexes = $repo2->getIndexes(); + $index2 = $indexes['col2.0']; $this->assertEquals(array($id), $index2->get('4', '==')); $id = 'doc456'; $doc->setId($id); diff --git a/test/JamesMoss/Flywheel/QueryExecuterTest.php b/test/JamesMoss/Flywheel/QueryExecuterTest.php index 659f21d..30fba9c 100644 --- a/test/JamesMoss/Flywheel/QueryExecuterTest.php +++ b/test/JamesMoss/Flywheel/QueryExecuterTest.php @@ -85,15 +85,15 @@ public function testSubPredicates() ->orWhere('language.0', '==', 'English'); }); - $qe = new QueryExecuter($this->getRepo('countries'), $pred, array(), array()); + $qe = new QueryExecuter($this->getRepo('countries'), $pred, array(), array('name ASC')); $result = $qe->run(); $this->assertEquals(3, $result->total()); - $this->assertEquals('Vatican City', $result->first()->name); + $this->assertEquals('Gibraltar', $result->first()->name); $this->assertEquals('San Marino', $result[1]->name); - $this->assertEquals('Gibraltar', $result[2]->name); + $this->assertEquals('Vatican City', $result[2]->name); } public function testInOperator() @@ -103,17 +103,15 @@ public function testInOperator() ->andWhere('population', '<', 40000) ->andWhere('language.0', 'IN', array('Italian', 'English')); - $qe = new QueryExecuter($this->getRepo('countries'), $pred, array(), array()); + $qe = new QueryExecuter($this->getRepo('countries'), $pred, array(), array('name ASC')); $result = $qe->run(); $this->assertEquals(3, $result->total()); - $this->assertEquals('Vatican City', $result->first()->name); - // The two following assertions doesn't work on PHP 5.4 and 5.5 - // probably because these versions need mockery 0.* instead of 1.* - // $this->assertEquals('San Marino', $result[1]->name); - // $this->assertEquals('Gibraltar', $result[2]->name); + $this->assertEquals('Gibraltar', $result->first()->name); + $this->assertEquals('San Marino', $result[1]->name); + $this->assertEquals('Vatican City', $result[2]->name); } public function testSimpleOrdering() From 4590b02993068d4764e3b813ddc98de009c9c518 Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Fri, 6 Dec 2019 01:12:23 +0100 Subject: [PATCH 12/13] feat(index): add regenerateIndexes function + test regenerate function + refactor testFindByIndex --- .../Flywheel/Index/IndexInterface.php | 7 ++ src/JamesMoss/Flywheel/Index/StoredIndex.php | 24 ++++--- src/JamesMoss/Flywheel/Repository.php | 7 ++ .../Flywheel/Index/HashIndexTest.php | 14 ++++ test/JamesMoss/Flywheel/QueryExecuterTest.php | 67 +++++++++++-------- 5 files changed, 82 insertions(+), 37 deletions(-) diff --git a/src/JamesMoss/Flywheel/Index/IndexInterface.php b/src/JamesMoss/Flywheel/Index/IndexInterface.php index 8686934..9097e33 100644 --- a/src/JamesMoss/Flywheel/Index/IndexInterface.php +++ b/src/JamesMoss/Flywheel/Index/IndexInterface.php @@ -41,4 +41,11 @@ public function get($value, $operator); * @return void */ public function update($id, $new, $old); + + /** + * Regenerate the index from the repository's documents. + * + * @return void; + */ + public function regenerate(); } diff --git a/src/JamesMoss/Flywheel/Index/StoredIndex.php b/src/JamesMoss/Flywheel/Index/StoredIndex.php index 12b56f1..5320c48 100644 --- a/src/JamesMoss/Flywheel/Index/StoredIndex.php +++ b/src/JamesMoss/Flywheel/Index/StoredIndex.php @@ -81,22 +81,30 @@ protected function needsData() if (isset($this->data)) { return; } - $this->initData(); if (file_exists($this->path)) { $fp = fopen($this->path, 'r'); $contents = fread($fp, filesize($this->path)); fclose($fp); $this->data = $this->formatter->decode($contents); } else { - foreach ($this->repository->findAll() as $doc) { - $docVal = $doc->getNestedProperty($this->field, $found); - if (!$found) { - continue; - } - $this->updateEntry($doc->getId(), $docVal, null); + $this->regenerate(); + } + } + + /** + * @inheritdoc + */ + public function regenerate() + { + $this->initData(); + foreach ($this->repository->findAll() as $doc) { + $docVal = $doc->getNestedProperty($this->field, $found); + if (!$found) { + continue; } - $this->flush(); + $this->updateEntry($doc->getId(), $docVal, null); } + $this->flush(); } /** diff --git a/src/JamesMoss/Flywheel/Repository.php b/src/JamesMoss/Flywheel/Repository.php index bbb8936..244191b 100644 --- a/src/JamesMoss/Flywheel/Repository.php +++ b/src/JamesMoss/Flywheel/Repository.php @@ -448,4 +448,11 @@ protected function getIdFromPath($path, $ext) return basename($path, '.' . $ext); } + public function regenerateIndexes() + { + foreach ($this->indexes as $index) { + $index->regenerate(); + } + } + } diff --git a/test/JamesMoss/Flywheel/Index/HashIndexTest.php b/test/JamesMoss/Flywheel/Index/HashIndexTest.php index 4e25ee0..a1f09ad 100644 --- a/test/JamesMoss/Flywheel/Index/HashIndexTest.php +++ b/test/JamesMoss/Flywheel/Index/HashIndexTest.php @@ -242,4 +242,18 @@ public function testInconsitentData() $this->assertEquals($query22->execute(), $query12->execute()); } + public function testRegenerate() + { + $id = 'doc1'; + $doc = new Document(array('col1' => 'val1')); + $doc->setId($id); + $this->repo->store($doc); + $this->assertEquals(array($id), $this->index->get('val1', '==')); + $formatter = new JSON(); + file_put_contents($this->repo->getPathForDocument($id), $formatter->encode(array('col1' => 'val2'))); + $this->index->regenerate(); + $this->assertEquals(array(), $this->index->get('val1', '==')); + $this->assertEquals(array($id), $this->index->get('val2', '==')); + } + } \ No newline at end of file diff --git a/test/JamesMoss/Flywheel/QueryExecuterTest.php b/test/JamesMoss/Flywheel/QueryExecuterTest.php index 30fba9c..6941ba0 100644 --- a/test/JamesMoss/Flywheel/QueryExecuterTest.php +++ b/test/JamesMoss/Flywheel/QueryExecuterTest.php @@ -191,36 +191,37 @@ public function testOrderingById() $this->assertEquals('Djibouti', $result[2]->name); } - public function testFindByIndex() - { - foreach(array('==', '!=') as $operator) { - $pred = $this->getPredicate() - ->where('region', $operator, 'Europe'); - $options = array( - 'indexes' => array( - 'region' => '\JamesMoss\Flywheel\Index\HashIndex' - ) - ); - $n = 5; - - $qe = new QueryExecuter($this->getRepo('countries', $options), $pred, array(), array()); - $start = microtime(true); - for ($i=0; $i < $n; $i++) { - $withIndex = $qe->run(); - } - $timeWithIndex = microtime(true) - $start; - - $qe = new QueryExecuter($this->getRepo('countries'), $pred, array(), array()); - $start = microtime(true); - for ($i=0; $i < $n; $i++) { - $withoutIndex = $qe->run(); - } - $timeWithoutIndex = microtime(true) - $start; - - $this->assertSameSize($withoutIndex, $withIndex); - $this->assertEqualsUnordered(get_object_vars($withoutIndex), get_object_vars($withIndex)); - $this->assertLessThan($timeWithoutIndex, $timeWithIndex); + /** + * @dataProvider hashIndexOperatorsProvider + */ + public function testFindByIndex($operator) + { + $pred = $this->getPredicate() + ->where('region', $operator, 'Europe'); + $options = array( + 'indexes' => array( + 'region' => '\JamesMoss\Flywheel\Index\HashIndex' + ) + ); + $n = 5; + + $qe = new QueryExecuter($this->getRepo('countries', $options), $pred, array(), array()); + $start = microtime(true); + for ($i=0; $i < $n; $i++) { + $withIndex = $qe->run(); + } + $timeWithIndex = microtime(true) - $start; + + $qe = new QueryExecuter($this->getRepo('countries'), $pred, array(), array()); + $start = microtime(true); + for ($i=0; $i < $n; $i++) { + $withoutIndex = $qe->run(); } + $timeWithoutIndex = microtime(true) - $start; + + $this->assertSameSize($withoutIndex, $withIndex); + $this->assertEqualsUnordered(get_object_vars($withoutIndex), get_object_vars($withIndex)); + $this->assertLessThan($timeWithoutIndex, $timeWithIndex); } @@ -236,4 +237,12 @@ protected function getPredicate() { return new Predicate(); } + + public function hashIndexOperatorsProvider() + { + return array( + array('=='), + array('!='), + ); + } } From 4c03c7642c9cab422aa88a5b1874413b05a504c0 Mon Sep 17 00:00:00 2001 From: n-peugnet Date: Sat, 14 Dec 2019 19:08:01 +0100 Subject: [PATCH 13/13] docs: add indexes section in readme --- readme.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 8bf1423..8187c60 100644 --- a/readme.md +++ b/readme.md @@ -221,11 +221,13 @@ $posts = $repo->query() ## Config options - - `formatter`. See [Formats](https://github.com/jamesmoss/flywheel#formats) section of this readme. Defaults to an + - `formatter`. See [Formats](#formats) section of this readme. Defaults to an instance of `JamesMoss\Flywheel\Formatter\JSON`. - `query_class`. The name of the class that gets returned from `Repository::query()`. By default, Flywheel detects if you have APC or APCu installed and uses `CachedQuery` class if applicable, otherwise it just uses `Query`. - `document_class`. The name of the class to use when hydrating documenst from the filesystem. Must implement `JamesMoss\Flywheel\DocumentInterface`. Defaults to `JamesMoss\Flywheel\Document`. + - `indexes`. See [Indexes](#indexes) section of this readme. Defaults to an + instance of `JamesMoss\Flywheel\Formatter\JSON`. ## Formats @@ -252,12 +254,35 @@ The following formatter classes are available. **Important** If you use the `YAML` or `Markdown` formatters when using the `--no-dev` flag in Composer you'll need to manually add `mustangostang\spyc` to your `composer.json`. Flywheel tries to keep it's dependencies to a minimum. -If you write your own formatter it must implement `JamesMoss\Flywheel\Formatter\Format`. +If you write your own formatter it must implement `JamesMoss\Flywheel\Formatter\FormatInterface`. + +## Indexes + +To speed up the queries on some specific fields you can add indexes on these fields. +There are different types of index, each specialized on certain queries: + +- `HashIndex` - supports only `==` and `!=` operators. + +If a query cannot be executed only on indexes it will fallback to the default behaviour, which is opening all the files of the repository. + +**Important** Indexes maintain their own copy of the data so you won't be able to manually edit the files while keeping the consistency of the database anymore. + +```php +use JamesMoss\Flywheel\Index\HashIndex; + +$config = new Config('/path/to/writable/directory', array( + 'indexes' => { + 'fieldname' => HashIndex::class + }, +)) +``` + +If you write your own index it must implement `JamesMoss\Flywheel\Index\IndexInterface`. ## Todo - More caching around `Repository::findAll`. -- Indexing. +- More Indexes. - HHVM support. - Abstract the filesystem, something like Gaufrette or Symfony's Filesystem component? - Events system.