diff --git a/src/Database/Query.php b/src/Database/Query.php new file mode 100644 index 000000000..00a56e8f7 --- /dev/null +++ b/src/Database/Query.php @@ -0,0 +1,179 @@ +attribute = $attribute; + $this->operator = $operator; + $this->values = $values; + } + + /** + * Get attribute + * + * @return string + */ + public function getAttribute(): string + { + return $this->attribute; + } + + /** + * Get operator + * + * @return string + */ + public function getOperator(): string + { + return $this->operator; + } + + /** + * Get operand + * + * @return mixed + */ + public function getValues() + { + return $this->values; + } + + /** + * Get all query details as array + * + * @return array + */ + public function getQuery(): array + { + return [ + 'attribute' => $this->attribute, + 'operator' => $this->operator, + 'values' => $this->values, + ]; + } + + /** + * Parse query filter + * + * @param string $filter + * + * @return Query + * */ + public static function parse(string $filter): Query + { + // get index of open parentheses + $end = mb_strpos($filter, '('); + // count stanzas by only counting '.' that come before open parentheses + $stanzas = mb_substr_count(mb_substr($filter, 0, $end), ".") + 1; + + // TODO@kodumbeats handle relations between collections, e.g. if($stanzas > 2) + switch ($stanzas) { + case 2: + // use limit param to ignore '.' in $expression + $input = explode('.', $filter, $stanzas); + $attribute = $input[0]; + $expression = $input[1]; + [$operator, $values] = self::parseExpression($expression); + break; + } + + return new Query($attribute, $operator, $values); + } + + /** + * Get attribute key-value from query expression + * $expression: string with format 'operator(value)' + * + * @param string $expression + * + * @return (string|array)[] + */ + protected static function parseExpression(string $expression): array + { + //find location of parentheses in expression + + /** @var int */ + $start = mb_strpos($expression, '('); + /** @var int */ + $end = mb_strrpos($expression, ')'); + + //extract the query method + + /** @var string */ + $operator = mb_substr($expression, 0, $start); + + //grab everything inside parentheses + + /** @var mixed */ + $value = mb_substr($expression, + ($start + 1), /* exclude open paren*/ + ($end - $start - 1) /* exclude closed paren*/ + ); + + // Explode comma-separated values + + $values = explode(',', $value); + + // Cast $value type + + $values = array_map(function ($value) { + + // Trim whitespace from around $value + + $value = trim($value); + + switch (true) { + // type casted to int or float by "+" operator + case is_numeric($value): + return $value + 0; + + // since (bool)"false" returns true, check bools manually + case $value === 'true': + return true; + + case $value === 'false': + return false; + + // need special case to cast (null) as null, not string + case $value === 'null': + return null; + + default: + // strip escape characters + $value = stripslashes($value); + // trim leading and tailing quotes + return trim($value, '\'"'); + } + + }, $values); + + return [$operator, $values]; + } +} diff --git a/tests/Database/Base.php b/tests/Database/Base.php index b5a93ad35..f37fac5a2 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -1564,7 +1564,6 @@ public function decodeTest() $this->assertEquals('1', '1'); } - /** * @depends testCreateDocument */ diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php new file mode 100644 index 000000000..f4811e92d --- /dev/null +++ b/tests/Database/QueryTest.php @@ -0,0 +1,124 @@ +assertEquals('title', $query->getAttribute()); + $this->assertEquals('equal', $query->getOperator()); + $this->assertContains('Iron Man', $query->getValues()); + } + + public function testParse() + { + $query = Query::parse('title.equal("Iron Man")'); + + $this->assertEquals('title', $query->getAttribute()); + $this->assertEquals('equal', $query->getOperator()); + $this->assertContains('Iron Man', $query->getValues()); + + $query = Query::parse('year.lesser(2001)'); + + $this->assertEquals('year', $query->getAttribute()); + $this->assertEquals('lesser', $query->getOperator()); + $this->assertContains(2001, $query->getValues()); + + $query = Query::parse('published.equal(true)'); + + $this->assertEquals('published', $query->getAttribute()); + $this->assertEquals('equal', $query->getOperator()); + $this->assertContains(true, $query->getValues()); + + $query = Query::parse('published.equal(false)'); + + $this->assertEquals('published', $query->getAttribute()); + $this->assertEquals('equal', $query->getOperator()); + $this->assertContains(false, $query->getValues()); + + $query = Query::parse('actors.notContains( " Johnny Depp ", " Brad Pitt" , "Al Pacino")'); + + $this->assertEquals('actors', $query->getAttribute()); + $this->assertEquals('notContains', $query->getOperator()); + $this->assertContains(' Johnny Depp ', $query->getValues()); + $this->assertContains(' Brad Pitt', $query->getValues()); + $this->assertContains('Al Pacino', $query->getValues()); + + $query = Query::parse('actors.equal("Brad Pitt", "Johnny Depp")'); + + $this->assertEquals('actors', $query->getAttribute()); + $this->assertEquals('equal', $query->getOperator()); + $this->assertContains('Brad Pitt', $query->getValues()); + $this->assertContains('Johnny Depp', $query->getValues()); + + $query = Query::parse('writers.contains("Tim O\'Reilly")'); + + $this->assertEquals('writers', $query->getAttribute()); + $this->assertEquals('contains', $query->getOperator()); + $this->assertContains("Tim O'Reilly", $query->getValues()); + + $query = Query::parse('score.greater(8.5)'); + + $this->assertEquals('score', $query->getAttribute()); + $this->assertEquals('greater', $query->getOperator()); + $this->assertContains(8.5, $query->getValues()); + + $query = Query::parse('director.notEqual("null")'); + + $this->assertEquals('director', $query->getAttribute()); + $this->assertEquals('notEqual', $query->getOperator()); + $this->assertContains('null', $query->getValues()); + + $query = Query::parse('director.notEqual(null)'); + + $this->assertEquals('director', $query->getAttribute()); + $this->assertEquals('notEqual', $query->getOperator()); + $this->assertContains(null, $query->getValues()); + } + + public function testGetAttribute() + { + $query = Query::parse('title.equal("Iron Man")'); + + $this->assertEquals('title', $query->getAttribute()); + } + + public function testGetOperator() + { + $query = Query::parse('title.equal("Iron Man")'); + + $this->assertEquals('equal', $query->getOperator()); + } + + public function testGetValue() + { + $query = Query::parse('title.equal("Iron Man")'); + + $this->assertContains('Iron Man', $query->getValues()); + } + + public function testGetQuery() + { + $query = Query::parse('title.equal("Iron Man")')->getQuery(); + + $this->assertEquals('title', $query['attribute']); + $this->assertEquals('equal', $query['operator']); + $this->assertContains('Iron Man', $query['values']); + } + +} \ No newline at end of file