diff --git a/.travis.yml b/.travis.yml index 6fa84f9..b70a95a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,14 +11,14 @@ php: - 7.0 env: - - DB=MYSQL + - DB=MYSQL CORE_RELEASE=3.2 matrix: include: - php: 5.6 - env: DB=MYSQL + env: DB=MYSQL CORE_RELEASE=3.2 - php: 5.6 - env: DB=PGSQL + env: DB=PGSQL CORE_RELEASE=3.3 allow_failures: - php: 7.0 diff --git a/README.md b/README.md index e759d9f..584e884 100644 --- a/README.md +++ b/README.md @@ -8,33 +8,34 @@ which JSON can be stored and from which it can be queried. Once stored, the module exposes a simple query API based on the [JSON operators found in Postgres v9.2+](https://www.postgresql.org/docs/9.5/static/functions-json.html), but with some modifications: -In Postgres both the `->` and `->>` operators act as a string and integer key matcherx on a JSON source as array or object. The module -however treats both source types the same - they are after all *both* JSON so `->` is used as an **Integer Matcher** and `->>` as a string matcher. +In Postgres both the `->` and `->>` operators act as string and integer key matchers on a JSON array or object. The module +however treats both source types the same - they are after all *both JSON* so `->` is used as an **Integer Matcher** and `->>` as a string matcher +*regardless* of the "type" of JSON stored. -In Postgress the `#>` patch match operator can act as an object or text matcher, but again, the module wishes to simplify things and as such +In Postgress the `#>` path match operator can act as an object or a text matcher, but again, the module wishes to simplify things and as such the `#>` operator is *just a simple path matcher*. I see nothing but confusion if the same operator were to be treated differently -depending on the format of the source data. +depending on the format of the source data but am prepared for discussion on it if any were forthcoming. Note: This module's query API is based on a relatively simple JSON to array conversion principle. -It does *not* use Postgres' or MySQL's native JSON operators. The aim however +It does *not* use Postgres' or MySQL's JSON operators at the ORM level. The aim however is to allow dev's to use their preferred DB's syntax, and to this end you can set the module into `mysql` or `postgres` mode using SS config: ```yml JSONText: - backend: mysql + backend: postgres ``` -Note: The module default is to use `postgres` which is also the only backend that will work. +Note: The module default is to use `postgres` which is also the only backend that will work at the moment. # Stability This is currently *alpha software*. At time of writing (June 2016) there is -only partial support for the `->` and `->>` operators and although well-tested, -they are far from complete (or even correct). +support for the `->` (Int matcher), `->>` (String matcher) and `#>` (Path matcher) operators and although well-tested, +they are far from complete. This leads me to.. diff --git a/code/models/JSONBackend.php b/code/models/JSONBackend.php index 4593c82..6f1620a 100644 --- a/code/models/JSONBackend.php +++ b/code/models/JSONBackend.php @@ -69,7 +69,7 @@ public function __construct($key, $val, $idx, $operator, $operand, $jsonText) /** * Match on keys by INT. - * + * * @return array */ public function matchIfKeyIsInt() @@ -83,7 +83,7 @@ public function matchIfKeyIsInt() /** * Match on keys by STRING. - * + * * @return array */ public function matchIfKeyIsStr() @@ -101,6 +101,8 @@ public function matchIfKeyIsStr() * * @return array * @throws \JSONText\Exceptions\JSONTextException + * @todo Naively only returns the first match. But what about where source JSON has legit duplicate keys? We need + * to return an array of matches.. */ public function matchOnPath() { @@ -132,6 +134,16 @@ public function matchOnPath() return $this->val[$vals[0]]; } +/* if ($this->key === $keys[0] && is_array($this->val) && !empty($this->val[$vals[0]])) { + $this->jsonText->updateCache($this->val[$vals[0]]); + }*/ + +/* if (count($this->jsonText->cache) === 1) { + return $this->jsonText->cache[0]; + }*/ + + // return $this->jsonText->cache; + return []; } diff --git a/code/models/fieldtypes/JSONText.php b/code/models/fieldtypes/JSONText.php index dda71db..6e33178 100644 --- a/code/models/fieldtypes/JSONText.php +++ b/code/models/fieldtypes/JSONText.php @@ -66,6 +66,15 @@ class JSONText extends \StringField * @var \RecursiveIteratorIterator */ protected $data; + + /** + * @var array + */ + protected $cache = []; + + public function updateCache($val) { + $this->cache[] = $val; + } /** * Returns an input field. @@ -130,7 +139,7 @@ public function setReturnType($type) { if (!in_array($type, ['json', 'array'])) { $msg = 'Bad type: ' . $type . ' passed to ' . __FUNCTION__; - throw new \JSONText\Exceptions\JSONTextException($msg); + throw new JSONTextException($msg); } $this->returnType = $type; @@ -158,7 +167,7 @@ public function getValueAsIterable() if (!$this->isJson($json)) { $msg = 'DB data is munged.'; - throw new \JSONText\Exceptions\JSONTextException($msg); + throw new JSONTextException($msg); } if (!$this->data) { @@ -169,7 +178,16 @@ public function getValueAsIterable() } return $this->data; + } + /** + * Returns the value of this field as a flattened array + * + * @return array + */ + public function getValueAsArray() + { + return iterator_to_array($this->getValueAsIterable()); } /** @@ -272,7 +290,7 @@ public function nth($n) if (!is_int($n)) { $msg = 'Argument passed to ' . __FUNCTION__ . ' must be an integer.'; - throw new \JSONText\Exceptions\JSONTextException($msg); + throw new JSONTextException($msg); } $i = 0; @@ -306,7 +324,7 @@ public function query($operator, $operand) if (!$this->isValidOperator($operator)) { $msg = 'JSON operator: ' . $operator . ' is invalid.'; - throw new \JSONText\Exceptions\JSONTextException($msg); + throw new JSONTextException($msg); } $i = 0; @@ -350,7 +368,7 @@ private function marshallQuery($key, $val, $idx, $args) if (!in_array($operator, $operators)) { $msg = 'Invalid ' . $backend . ' operator: ' . $operator . ', used for JSON query.'; - throw new \JSONText\Exceptions\JSONTextException($msg); + throw new JSONTextException($msg); } foreach ($operators as $routine => $backendOperator) { @@ -365,7 +383,7 @@ private function marshallQuery($key, $val, $idx, $args) ]); if ($operator === $backendOperator && $result = $backendDBApiInst->$routine()) { - return $result; + return $result; } } diff --git a/tests/JSONTextTest.php b/tests/JSONTextTest.php index 7ff9af6..701b782 100644 --- a/tests/JSONTextTest.php +++ b/tests/JSONTextTest.php @@ -19,7 +19,7 @@ class JSONTextTest extends SapphireTest 'hash_simple' => MODULE_DIR . '/tests/fixtures/json/hash_simple.json', 'invalid' => MODULE_DIR . '/tests/fixtures/json/invalid.json', 'hash_deep' => MODULE_DIR . '/tests/fixtures/json/hash_deep.json', - 'hash_dupes' => MODULE_DIR . '/tests/fixtures/json/hash_duplicates.json', + 'hash_dupes' => MODULE_DIR . '/tests/fixtures/json/hash_duplicated.json', 'empty' => MODULE_DIR . '/tests/fixtures/json/empty.json' ]; @@ -247,6 +247,20 @@ public function testQuery_MatchPath_AsJSON() $this->assertEquals('{"fast":{"Kawasaki":"KR1S250"},"slow":{"Honda":"FS150"}}', $field->query('#>', '{"bikes":"japanese"}')); } + + /** + * Tests query() by means of path-matching using the Postgres path match operator: '#>' but where duplicate keys exist + * for different parent structures in source data + */ +/* public function testQuery_MatchPathDuplicate_AsArray() + { + // Hashed + $field = JSONText\Fields\JSONText::create('MyJSON'); + $field->setValue($this->getFixture('hash_dupes')); + $field->setReturnType('array'); + + $this->assertEquals([["Kawasaki" => "KR1S250"],["Subaru" => "Impreza"]], $field->query('#>', '{"japanese":"fast"}')); + }*/ /** * Get the contents of a fixture diff --git a/tests/fixtures/json/hash_duplicated.json b/tests/fixtures/json/hash_duplicated.json new file mode 100644 index 0000000..fddabd9 --- /dev/null +++ b/tests/fixtures/json/hash_duplicated.json @@ -0,0 +1,37 @@ +{ + "cars":{ + "american": + [ + "buick", + "oldsmobile" + ], + "british": + ["vauxhall","morris"], + "japanese": { + "fast": { + "Subaru": "Impreza" + }, + "slow": { + "Honda": "Civic" + } + } + }, + "planes":{ + "russian": + [ + "antonov", + "mig" + ], + "french":"airbus" + }, + "bikes":{ + "japanese":{ + "fast":{ + "Kawasaki":"KR1S250" + }, + "slow":{ + "Honda":"FS150" + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/json/hash_duplicates.json b/tests/fixtures/json/hash_duplicates.json deleted file mode 100644 index 289f38f..0000000 --- a/tests/fixtures/json/hash_duplicates.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "cars":{ - "american": - [ - "buick", - "oldsmobile" - ], - "british": - [ - "vauxhall", - "morris" - ] - }, - "planes":{ - "british":"airbus", - "french":"airbus" - } -} \ No newline at end of file