diff --git a/modules/common/src/Storage/SelectFactory.php b/modules/common/src/Storage/SelectFactory.php index 7862e22b1b..62d8699311 100644 --- a/modules/common/src/Storage/SelectFactory.php +++ b/modules/common/src/Storage/SelectFactory.php @@ -253,15 +253,33 @@ private function setQueryConditions(Query $query) { * A condition from the DKAN query object. */ private function addCondition($statementObj, $condition) { - if (!isset($condition->operator)) { - $condition->operator = '='; - } + $this->normalizeOperator($condition); $field = (isset($condition->collection) ? $condition->collection : $this->alias) . '.' . $condition->property; $statementObj->condition($field, $condition->value, strtoupper($condition->operator)); } + /** + * Fix any quirks in DKAN query object that won't translate well to SQL. + * + * @param object $condition + * A condition from the DKAN query object. + */ + private function normalizeOperator($condition) { + if (!isset($condition->operator)) { + $condition->operator = '='; + } + elseif ($condition->operator == 'contains') { + $condition->operator = 'like'; + $condition->value = "%{$condition->value}%"; + } + elseif ($condition->operator == 'starts with') { + $condition->operator = 'like'; + $condition->value = "{$condition->value}%"; + } + } + /** * Add a condition group to the database query. * diff --git a/modules/common/tests/src/Unit/Storage/QueryDataProvider.php b/modules/common/tests/src/Unit/Storage/QueryDataProvider.php index adef4ceeb6..124c19d988 100644 --- a/modules/common/tests/src/Unit/Storage/QueryDataProvider.php +++ b/modules/common/tests/src/Unit/Storage/QueryDataProvider.php @@ -19,6 +19,7 @@ class QueryDataProvider { const QUERY_OBJECT = 1; const SQL = 2; const EXCEPTION = 3; + const VALUES = 4; /** * @@ -34,6 +35,8 @@ public function getAllData($return): array { 'badExpressionOperandQuery', 'conditionQuery', 'likeConditionQuery', + 'containsConditionQuery', + 'startsWithConditionQuery', 'arrayConditionQuery', 'nestedConditionGroupQuery', 'sortQuery', @@ -51,6 +54,7 @@ public function getAllData($return): array { self::$test(self::QUERY_OBJECT), self::$test(self::SQL), self::$test(self::EXCEPTION), + self::$test(self::VALUES), ]; } return $data; @@ -70,6 +74,9 @@ public static function noPropertiesQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return []; } } @@ -88,6 +95,10 @@ public static function propertiesQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return []; + } } @@ -106,6 +117,9 @@ public static function badPropertyQuery($return) { case self::EXCEPTION: return "Bad query property"; + + case self::VALUES: + return []; } } @@ -124,6 +138,9 @@ public static function unsafePropertyQuery($return) { case self::EXCEPTION: return "Unsafe property name: l.field3"; + + case self::VALUES: + return []; } } @@ -175,6 +192,9 @@ public static function expressionQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return []; } } @@ -214,6 +234,9 @@ public static function nestedExpressionQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return []; } } @@ -240,6 +263,9 @@ public static function badExpressionOperandQuery($return) { case self::EXCEPTION: return "Only basic arithmetic expressions and basic SQL functions currently supported."; + + case self::VALUES: + return []; } } @@ -264,6 +290,9 @@ public static function conditionQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return ['value']; } } @@ -289,9 +318,68 @@ public static function likeConditionQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return ['%value%']; + } + + } + + + /** + * + */ + public static function containsConditionQuery($return) { + switch ($return) { + case self::QUERY_OBJECT: + $query = new Query(); + $query->conditions = [ + (object) [ + "collection" => "t", + "property" => "field1", + "value" => "value", + "operator" => "contains", + ], + ]; + return $query; + + case self::SQL: + return "WHERE t.field1 LIKE :db_condition_placeholder_0"; + + case self::EXCEPTION: + return ''; + + case self::VALUES: + return ['%value%']; + } + } + + public static function startsWithConditionQuery($return) { + switch ($return) { + case self::QUERY_OBJECT: + $query = new Query(); + $query->conditions = [ + (object) [ + "collection" => "t", + "property" => "field1", + "value" => "value", + "operator" => "starts with", + ], + ]; + return $query; + + case self::SQL: + return "WHERE t.field1 LIKE :db_condition_placeholder_0"; + + case self::EXCEPTION: + return ''; + + case self::VALUES: + return ['value%']; } } + /** * */ @@ -314,7 +402,11 @@ public static function arrayConditionQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return [1, 5]; } + } /** @@ -359,7 +451,11 @@ public static function nestedConditionGroupQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return ['value1', 'value2', 'value3']; } + } /** @@ -393,6 +489,9 @@ public static function sortQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return []; } } @@ -411,6 +510,9 @@ public static function badSortQuery($return) { case self::EXCEPTION: return "Invalid sort."; + + case self::VALUES: + return []; } } @@ -429,6 +531,9 @@ public static function offsetQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return []; } } @@ -448,6 +553,9 @@ public static function limitOffsetQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return []; } } @@ -479,6 +587,9 @@ public static function joinsQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return []; } } @@ -517,6 +628,9 @@ public static function joinWithPropertiesFromBothQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return []; } } @@ -535,6 +649,9 @@ public static function countQuery($return) { case self::EXCEPTION: return ''; + + case self::VALUES: + return []; } } @@ -584,6 +701,9 @@ public static function groupByQuery(int $returnType) { case self::EXCEPTION: return ''; + + case self::VALUES: + return ['value']; } throw new \UnexpectedValueException('Unknown return type provided.'); diff --git a/modules/common/tests/src/Unit/Storage/SelectFactoryTest.php b/modules/common/tests/src/Unit/Storage/SelectFactoryTest.php index 38bca09050..58a7564630 100644 --- a/modules/common/tests/src/Unit/Storage/SelectFactoryTest.php +++ b/modules/common/tests/src/Unit/Storage/SelectFactoryTest.php @@ -27,7 +27,7 @@ class SelectFactoryTest extends TestCase { * * @dataProvider \Drupal\Tests\common\Unit\Storage\QueryDataProvider::getAllData() */ - public function testQuery(Query $query, string $sql, string $message) { + public function testQuery(Query $query, string $sql, string $message, array $values = []) { if ($message) { $this->expectExceptionMessage($message); $this->selectFactory->create($query); @@ -35,6 +35,10 @@ public function testQuery(Query $query, string $sql, string $message) { else { $db_query = $this->selectFactory->create($query); $this->assertStringContainsString($sql, $this->selectToString($db_query)); + + if (!empty($values)) { + $this->assertEquals($values, array_values($db_query->arguments())); + } } } diff --git a/modules/datastore/docs/query.json b/modules/datastore/docs/query.json index 96a48b5244..97da7fb29a 100644 --- a/modules/datastore/docs/query.json +++ b/modules/datastore/docs/query.json @@ -194,8 +194,21 @@ "operator": { "type": "string", "description": "Condition operator", - "enum": [ "=", "<>", "<", "<=", ">", ">=", "like", "between", "in", "not in" ], - "default": "=" + "enum": [ + "=", + "<>", + "<", + "<=", + ">", + ">=", + "like", + "between", + "in", + "not in", + "contains", + "starts with" + ], + "default": "=" } }, "required": [ "property", "value" ]