From 738870d9fc0133dc29b6241dbf075579b86e43db Mon Sep 17 00:00:00 2001 From: Ilias Dimopoulos Date: Sun, 28 Mar 2021 20:15:54 +0300 Subject: [PATCH 1/8] ISAICP-6242: Support NOT EXISTS in SPARQL. --- composer.json | 3 ++- composer.lock | 17 +++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 056de2029e..d10eb72f62 100644 --- a/composer.json +++ b/composer.json @@ -341,7 +341,8 @@ "drupal/sparql_entity_storage": { "Bundle classes support @see https://github.com/ec-europa/sparql_entity_storage/pull/11": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/11.diff", "Allow routes to filter by bundle @see https://github.com/ec-europa/sparql_entity_storage/pull/32": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/32.diff", - "Allow to pass options to the serializer @see https://github.com/idimopoulos/sparql_entity_storage/pull/3": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/3.diff" + "Allow to pass options to the serializer @see https://github.com/idimopoulos/sparql_entity_storage/pull/3": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/3.diff", + "Support NOT EXISTS query @see https://github.com/idimopoulos/sparql_entity_storage/pull/4": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/4.diff" }, "drupal/subpathauto": { "Subpaths with redirect not resolved @see https://www.drupal.org/project/subpathauto/issues/3175637": "https://git.drupalcode.org/project/subpathauto/-/merge_requests/1/diffs.diff" diff --git a/composer.lock b/composer.lock index 1cb6dd8e86..06f4c7c876 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "602a42b94cdd63475ac883f392f176f6", + "content-hash": "48a84a6b0754ef1a4db816a0be4dd0ed", "packages": [ { "name": "SEMICeu/adms-ap_validator", @@ -3846,14 +3846,6 @@ { "name": "borisson_", "homepage": "https://www.drupal.org/user/2393360" - }, - { - "name": "drunken monkey", - "homepage": "https://www.drupal.org/user/205582" - }, - { - "name": "mkalkbrenner", - "homepage": "https://www.drupal.org/user/124705" } ], "description": "The Facet module allows site builders to easily create and manage faceted search interfaces.", @@ -5199,7 +5191,7 @@ "extra": { "drupal": { "version": "8.x-1.0-alpha2", - "datestamp": "1613598911", + "datestamp": "1613598883", "security-coverage": { "status": "not-covered", "message": "Project has not opted into security advisory coverage!" @@ -6654,7 +6646,8 @@ "patches_applied": { "Bundle classes support @see https://github.com/ec-europa/sparql_entity_storage/pull/11": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/11.diff", "Allow routes to filter by bundle @see https://github.com/ec-europa/sparql_entity_storage/pull/32": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/32.diff", - "Allow to pass options to the serializer @see https://github.com/idimopoulos/sparql_entity_storage/pull/3": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/3.diff" + "Allow to pass options to the serializer @see https://github.com/idimopoulos/sparql_entity_storage/pull/3": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/3.diff", + "Support NOT EXISTS query @see https://github.com/idimopoulos/sparql_entity_storage/pull/4": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/4.diff" } }, "autoload": { @@ -10228,7 +10221,7 @@ "reference": "master" }, "type": "library", - "time": "2020-08-03T19:59:39+00:00" + "time": "2020-11-25T21:59:37+00:00" }, { "name": "stack/builder", From 83dc4589f462aa52e0b93af720e57d3903ad1267 Mon Sep 17 00:00:00 2001 From: Ilias Dimopoulos Date: Mon, 5 Apr 2021 14:46:15 +0300 Subject: [PATCH 2/8] ISAICP-6242: Attempt to show the entire query in debug. --- .../Database/joinup_sparql/Connection.php | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php b/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php index ffb9468f4b..edcdb098a4 100644 --- a/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php +++ b/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php @@ -9,6 +9,7 @@ use Drupal\sparql_entity_storage\Exception\SparqlQueryException; use EasyRdf\Http; use EasyRdf\Http\Client as HttpClient; +use EasyRdf\Http\Exception as EasyRdfException; use EasyRdf\Sparql\Client; use EasyRdf\Sparql\Result; @@ -39,6 +40,56 @@ public function query(string $query, array $args = [], array $options = []): Res } } + /** + * Execute the query against the endpoint. + * + * @param string $query + * The string query to execute. + * @param array $args + * An array of arguments for the query. + * @param array $options + * An associative array of options to control how the query is run. + * + * @return \EasyRdf\Sparql\Result|\EasyRdf\Graph + * The query result. + * + * @throws \InvalidArgumentException + * If $args value is passed but arguments replacement is not yet + * supported. To be removed in #55. + * @throws \Drupal\sparql_entity_storage\Exception\SparqlQueryException + * Exception during query execution, e.g. timeout. + * + * @see https://github.com/ec-europa/sparql_entity_storage/issues/1 + */ + protected function doQuery(string $query, array $args = [], array $options = []) { + // @todo Remove this in #1. + // @see https://github.com/ec-europa/sparql_entity_storage/issues/1 + if ($args) { + throw new \InvalidArgumentException('Replacement arguments are not yet supported.'); + } + + if ($this->logger) { + $query_start = microtime(TRUE); + } + + try { + // @todo Implement argument replacement in #1. + // @see https://github.com/ec-europa/sparql_entity_storage/issues/1 + $results = $this->easyRdfClient->query($query); + } + catch (EasyRdfException $exception) { + // Re-throw the exception, but with the query as message. + throw new SparqlQueryException('Execution of query failed: ' . htmlentities($query)); + } + + if ($this->logger) { + $query_end = microtime(TRUE); + $this->log($query, $args, $query_end - $query_start); + } + + return $results; + } + /** * {@inheritdoc} */ From 3c09621c02f2afaecf37a6bd53cd36922bb69d75 Mon Sep 17 00:00:00 2001 From: Ilias Dimopoulos Date: Mon, 5 Apr 2021 15:52:50 +0300 Subject: [PATCH 3/8] ISAICP-6242: Try to run 3 times before failing the query. --- .../Database/joinup_sparql/Connection.php | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php b/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php index edcdb098a4..3a652824f7 100644 --- a/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php +++ b/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php @@ -27,17 +27,25 @@ class Connection extends BaseConnection implements ConnectionInterface { * {@inheritdoc} */ public function query(string $query, array $args = [], array $options = []): Result { - try { - return parent::query($query); - } - catch (SparqlQueryException $e) { - // During a Virtuoso checkpoint, the server locks down, causing HTTP - // requests on the SPARQL endpoint to fail with a 404 response. We wait a - // reasonable amount of time and then we retry one more time. - // @see http://docs.openlinksw.com/virtuoso/checkpoint/ - sleep(5); - return parent::query($query); - } + $attempts = 2; + do { + $success = TRUE; + try { + return parent::query($query); + } + catch (SparqlQueryException $e) { + // During a Virtuoso checkpoint, the server locks down, causing HTTP + // requests on the SPARQL endpoint to fail with a 404 response. We wait + // a reasonable amount of time and then we retry one more time. + // @see http://docs.openlinksw.com/virtuoso/checkpoint/ + sleep(5); + if ($attempts === 0) { + throw new \Exception($e->getMessage()); + } + $attempts--; + $success = FALSE; + } + } while (!$success); } /** From befa7b65f66516da32bd8e6e98244db7297821d5 Mon Sep 17 00:00:00 2001 From: Ilias Dimopoulos Date: Tue, 6 Apr 2021 23:04:28 +0300 Subject: [PATCH 4/8] ISAICP-6242: Use a newer version of the sparql patch. --- composer.json | 2 +- composer.lock | 4 +- ...ql_entity_storage_support_not_exists.patch | 195 ++++++++++++++++++ 3 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 resources/patch/sparql_entity_storage_support_not_exists.patch diff --git a/composer.json b/composer.json index d10eb72f62..fbf4bf1e3a 100644 --- a/composer.json +++ b/composer.json @@ -342,7 +342,7 @@ "Bundle classes support @see https://github.com/ec-europa/sparql_entity_storage/pull/11": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/11.diff", "Allow routes to filter by bundle @see https://github.com/ec-europa/sparql_entity_storage/pull/32": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/32.diff", "Allow to pass options to the serializer @see https://github.com/idimopoulos/sparql_entity_storage/pull/3": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/3.diff", - "Support NOT EXISTS query @see https://github.com/idimopoulos/sparql_entity_storage/pull/4": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/4.diff" + "Support NOT EXISTS query @see https://github.com/idimopoulos/sparql_entity_storage/pull/4": "resources/patch/sparql_entity_storage_support_not_exists.patch" }, "drupal/subpathauto": { "Subpaths with redirect not resolved @see https://www.drupal.org/project/subpathauto/issues/3175637": "https://git.drupalcode.org/project/subpathauto/-/merge_requests/1/diffs.diff" diff --git a/composer.lock b/composer.lock index 06f4c7c876..beb1eb182d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "48a84a6b0754ef1a4db816a0be4dd0ed", + "content-hash": "0d022833de83f35463f006cff5afa36b", "packages": [ { "name": "SEMICeu/adms-ap_validator", @@ -6647,7 +6647,7 @@ "Bundle classes support @see https://github.com/ec-europa/sparql_entity_storage/pull/11": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/11.diff", "Allow routes to filter by bundle @see https://github.com/ec-europa/sparql_entity_storage/pull/32": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/32.diff", "Allow to pass options to the serializer @see https://github.com/idimopoulos/sparql_entity_storage/pull/3": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/3.diff", - "Support NOT EXISTS query @see https://github.com/idimopoulos/sparql_entity_storage/pull/4": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/4.diff" + "Support NOT EXISTS query @see https://github.com/idimopoulos/sparql_entity_storage/pull/4": "resources/patch/sparql_entity_storage_support_not_exists.patch" } }, "autoload": { diff --git a/resources/patch/sparql_entity_storage_support_not_exists.patch b/resources/patch/sparql_entity_storage_support_not_exists.patch new file mode 100644 index 0000000000..9f55943558 --- /dev/null +++ b/resources/patch/sparql_entity_storage_support_not_exists.patch @@ -0,0 +1,195 @@ +diff --git a/src/Entity/Query/Sparql/Query.php b/src/Entity/Query/Sparql/Query.php +index 9215946..3097e69 100644 +--- a/src/Entity/Query/Sparql/Query.php ++++ b/src/Entity/Query/Sparql/Query.php +@@ -355,6 +355,16 @@ class Query extends QueryBase implements SparqlQueryInterface { + if (!in_array($direction, ['ASC', 'DESC'])) { + throw new \RuntimeException('Only "ASC" and "DESC" are allowed as sort order.'); + } ++ ++ // Unlike the normal SQL queries where a column not defined can be used for ++ // sorting if exists in the table, in SPARQL, the sort argument must be ++ // defined in the WHERE clause. Any sort property, therefore, must will be ++ // included with an EXISTS condition. ++ // Also, the $idKey and $bundleKey properties cannot be added as triples as ++ // they cannot be the object of the triple. ++ if (!in_array($field, [$this->idKey, $this->bundleKey])) { ++ $this->exists($field); ++ } + return parent::sort($field, $direction, $langcode); + } + +diff --git a/src/Entity/Query/Sparql/SparqlCondition.php b/src/Entity/Query/Sparql/SparqlCondition.php +index 582416f..47e7201 100644 +--- a/src/Entity/Query/Sparql/SparqlCondition.php ++++ b/src/Entity/Query/Sparql/SparqlCondition.php +@@ -70,7 +70,7 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn + 'ENDS WITH' => ['prefix' => 'FILTER(STRENDS(', 'suffix' => '))'], + 'LIKE' => ['prefix' => 'FILTER(CONTAINS(', 'suffix' => '))'], + 'NOT LIKE' => ['prefix' => 'FILTER(!CONTAINS(', 'suffix' => '))'], +- 'EXISTS' => ['prefix' => 'FILTER EXISTS {', 'suffix' => '}'], ++ 'EXISTS' => ['prefix' => '', 'suffix' => ''], + 'NOT EXISTS' => ['prefix' => 'FILTER NOT EXISTS {', 'suffix' => '}'], + '<' => ['prefix' => '', 'suffix' => ''], + '>' => ['prefix' => '', 'suffix' => ''], +@@ -271,10 +271,6 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn + 'lang' => $lang, + 'column' => $column, + ]; +- +- if (!in_array($operator, ['EXISTS', 'NOT EXISTS'])) { +- $this->requiresDefaultPattern = FALSE; +- } + } + + return $this; +@@ -384,6 +380,7 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn + } + + $mappings = $this->fieldHandler->getFieldPredicates($entity_type_id, $field, $column); ++ $mappings = array_values(array_unique($mappings)); + $field_name = $field . '__' . $column; + if (count($mappings) === 1) { + $this->fieldMappings[$field_name] = reset($mappings); +@@ -396,10 +393,10 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn + // loaded by the database. There is no way that in a single request, + // the same predicate is found with a single and multiple mappings. + // There is no filter per bundle in the query. +- $this->fieldMappingConditions[] = [ ++ $this->fieldMappingConditions[$field_name] = [ + 'field' => $field, + 'column' => $column, +- 'value' => array_values(array_unique($mappings)), ++ 'value' => $mappings, + 'operator' => 'IN', + ]; + } +@@ -445,10 +442,11 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn + $field_name = $condition['field'] . '__' . $condition['column']; + $field_predicate = $this->fieldMappings[$field_name]; + $condition_string = self::ID_KEY . ' ' . $this->escapePredicate($field_predicate) . ' ' . SparqlArg::toVar($field_name); ++ $this->addConditionFragment($condition_string); + + $condition['value'] = SparqlArg::toResourceUris($condition['value']); + $condition['field'] = $field_predicate; +- $condition_string .= ' . ' . $this->compileValuesFilter($condition); ++ $condition_string = $this->compileValuesFilter($condition); + $this->addConditionFragment($condition_string); + } + } +@@ -503,8 +501,11 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn + break; + + case 'EXISTS': ++ $this->compileExists($condition); ++ break; ++ + case 'NOT EXISTS': +- $this->addConditionFragment($this->compileExists($condition)); ++ $this->compileNotExists($condition); + break; + + case 'CONTAINS': +@@ -589,18 +590,66 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn + } + + /** +- * Compiles a filter exists (or not exists) condition. ++ * Compiles a filter exists condition. ++ * ++ * Since a triple in SPARQL works just like EXISTS does, for EXISTS we add ++ * any condition missing from the field mapping fragments. + * + * @param array $condition + * An array that contains the 'field', 'value', 'operator' values. ++ */ ++ protected function compileExists(array $condition): void { ++ $field_predicate = $this->fieldMappings[$condition['field']]; ++ $condition_strings = []; ++ $condition_strings[] = self::ID_KEY . ' ' . $this->escapePredicate($field_predicate) . ' ' . SparqlArg::toVar($condition['field']); ++ ++ if (isset($this->fieldMappingConditions[$condition['field']])) { ++ $mapping_condition = $this->fieldMappingConditions[$condition['field']]; ++ $mapping_condition['value'] = SparqlArg::toResourceUris($mapping_condition['value']); ++ $mapping_condition['field'] = $field_predicate; ++ $condition_strings[] = $this->compileValuesFilter($mapping_condition); ++ } ++ ++ foreach ($condition_strings as $condition_string) { ++ if (array_search($condition_string, $this->conditionFragments) === FALSE) { ++ $this->addConditionFragment($condition_string); ++ } ++ } ++ } ++ ++ /** ++ * Compiles a filter not exists condition. + * +- * @return string +- * A condition fragment string. ++ * @param array $condition ++ * An array that contains the 'field', 'value', 'operator' values. + */ +- protected function compileExists(array $condition): string { ++ protected function compileNotExists(array $condition): void { + $prefix = self::$filterOperatorMap[$condition['operator']]['prefix']; + $suffix = self::$filterOperatorMap[$condition['operator']]['suffix']; +- return $prefix . self::ID_KEY . ' ' . $this->escapePredicate($this->fieldMappings[$condition['field']]) . ' ' . SparqlArg::toVar($condition['field']) . $suffix; ++ ++ $field_predicate = $this->fieldMappings[$condition['field']]; ++ $condition_strings = []; ++ $condition_strings[] = self::ID_KEY . ' ' . $this->escapePredicate($field_predicate) . ' ' . SparqlArg::toVar($condition['field']); ++ ++ if (isset($this->fieldMappingConditions[$condition['field']])) { ++ $mapping_condition = $this->fieldMappingConditions[$condition['field']]; ++ $mapping_condition['value'] = SparqlArg::toResourceUris($mapping_condition['value']); ++ $mapping_condition['field'] = $field_predicate; ++ $condition_strings[] = $this->compileValuesFilter($mapping_condition); ++ } ++ ++ foreach ($condition_strings as $condition_string) { ++ $key = array_search($condition_string, $this->conditionFragments); ++ // Since field mapping conditions act also as EXISTS (the triple patterns ++ // MUST exist), remove any pattern added in the mapping conditions so that ++ // only the negative condition below exists. ++ if ($key !== FALSE) { ++ unset($this->conditionFragments[$key]); ++ } ++ } ++ ++ $this->addConditionFragment($prefix . implode(' . ', $condition_strings) . $suffix); ++ + } + + /** +diff --git a/tests/src/Kernel/SparqlEntityQueryTest.php b/tests/src/Kernel/SparqlEntityQueryTest.php +index a8fe4b7..ace3e25 100644 +--- a/tests/src/Kernel/SparqlEntityQueryTest.php ++++ b/tests/src/Kernel/SparqlEntityQueryTest.php +@@ -448,6 +448,26 @@ class SparqlEntityQueryTest extends SparqlKernelTestBase { + ]; + } + ++ /** ++ * Tests the NOT EXISTS operator. ++ */ ++ public function testNotExists() { ++ $entity = SparqlTest::create([ ++ 'id' => 'http://fruit.example.com/not_exists', ++ 'title' => 'fruit title not exists', ++ 'type' => 'fruit', ++ ]); ++ $entity->save(); ++ $this->entities[] = $entity; ++ ++ $results = $this->getQuery() ++ ->condition('type', 'fruit') ++ ->notExists('text') ++ ->execute(); ++ ++ $this->assertNotEmpty($results); ++ } ++ + /** + * Asserts that arrays are identical. + */ From df8b00bc3a1af042c70db1b130618eecc820fc9b Mon Sep 17 00:00:00 2001 From: Ilias Dimopoulos Date: Fri, 9 Apr 2021 16:36:20 +0300 Subject: [PATCH 5/8] Revert "ISAICP-6242: Try to run 3 times before failing the query." This reverts commit 3c09621c02f2afaecf37a6bd53cd36922bb69d75. --- .../Database/joinup_sparql/Connection.php | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php b/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php index 3a652824f7..edcdb098a4 100644 --- a/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php +++ b/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php @@ -27,25 +27,17 @@ class Connection extends BaseConnection implements ConnectionInterface { * {@inheritdoc} */ public function query(string $query, array $args = [], array $options = []): Result { - $attempts = 2; - do { - $success = TRUE; - try { - return parent::query($query); - } - catch (SparqlQueryException $e) { - // During a Virtuoso checkpoint, the server locks down, causing HTTP - // requests on the SPARQL endpoint to fail with a 404 response. We wait - // a reasonable amount of time and then we retry one more time. - // @see http://docs.openlinksw.com/virtuoso/checkpoint/ - sleep(5); - if ($attempts === 0) { - throw new \Exception($e->getMessage()); - } - $attempts--; - $success = FALSE; - } - } while (!$success); + try { + return parent::query($query); + } + catch (SparqlQueryException $e) { + // During a Virtuoso checkpoint, the server locks down, causing HTTP + // requests on the SPARQL endpoint to fail with a 404 response. We wait a + // reasonable amount of time and then we retry one more time. + // @see http://docs.openlinksw.com/virtuoso/checkpoint/ + sleep(5); + return parent::query($query); + } } /** From 11e94b21e71e49c3fb5889907f50c47721d7459a Mon Sep 17 00:00:00 2001 From: Ilias Dimopoulos Date: Fri, 9 Apr 2021 16:36:30 +0300 Subject: [PATCH 6/8] Revert "ISAICP-6242: Attempt to show the entire query in debug." This reverts commit 83dc4589f462aa52e0b93af720e57d3903ad1267. --- .../Database/joinup_sparql/Connection.php | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php b/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php index edcdb098a4..ffb9468f4b 100644 --- a/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php +++ b/web/modules/custom/joinup_sparql/src/Driver/Database/joinup_sparql/Connection.php @@ -9,7 +9,6 @@ use Drupal\sparql_entity_storage\Exception\SparqlQueryException; use EasyRdf\Http; use EasyRdf\Http\Client as HttpClient; -use EasyRdf\Http\Exception as EasyRdfException; use EasyRdf\Sparql\Client; use EasyRdf\Sparql\Result; @@ -40,56 +39,6 @@ public function query(string $query, array $args = [], array $options = []): Res } } - /** - * Execute the query against the endpoint. - * - * @param string $query - * The string query to execute. - * @param array $args - * An array of arguments for the query. - * @param array $options - * An associative array of options to control how the query is run. - * - * @return \EasyRdf\Sparql\Result|\EasyRdf\Graph - * The query result. - * - * @throws \InvalidArgumentException - * If $args value is passed but arguments replacement is not yet - * supported. To be removed in #55. - * @throws \Drupal\sparql_entity_storage\Exception\SparqlQueryException - * Exception during query execution, e.g. timeout. - * - * @see https://github.com/ec-europa/sparql_entity_storage/issues/1 - */ - protected function doQuery(string $query, array $args = [], array $options = []) { - // @todo Remove this in #1. - // @see https://github.com/ec-europa/sparql_entity_storage/issues/1 - if ($args) { - throw new \InvalidArgumentException('Replacement arguments are not yet supported.'); - } - - if ($this->logger) { - $query_start = microtime(TRUE); - } - - try { - // @todo Implement argument replacement in #1. - // @see https://github.com/ec-europa/sparql_entity_storage/issues/1 - $results = $this->easyRdfClient->query($query); - } - catch (EasyRdfException $exception) { - // Re-throw the exception, but with the query as message. - throw new SparqlQueryException('Execution of query failed: ' . htmlentities($query)); - } - - if ($this->logger) { - $query_end = microtime(TRUE); - $this->log($query, $args, $query_end - $query_start); - } - - return $results; - } - /** * {@inheritdoc} */ From e45a1976be8fc9b4c355461f34eb6d0c601b0eeb Mon Sep 17 00:00:00 2001 From: Ilias Dimopoulos Date: Fri, 9 Apr 2021 16:40:46 +0300 Subject: [PATCH 7/8] ISAICP-6242: Cleanup the queries a bit. --- ...ql_entity_storage_support_not_exists.patch | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/resources/patch/sparql_entity_storage_support_not_exists.patch b/resources/patch/sparql_entity_storage_support_not_exists.patch index 9f55943558..6795866a4c 100644 --- a/resources/patch/sparql_entity_storage_support_not_exists.patch +++ b/resources/patch/sparql_entity_storage_support_not_exists.patch @@ -20,7 +20,7 @@ index 9215946..3097e69 100644 } diff --git a/src/Entity/Query/Sparql/SparqlCondition.php b/src/Entity/Query/Sparql/SparqlCondition.php -index 582416f..47e7201 100644 +index 582416f..cd6de5b 100644 --- a/src/Entity/Query/Sparql/SparqlCondition.php +++ b/src/Entity/Query/Sparql/SparqlCondition.php @@ -70,7 +70,7 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn @@ -32,18 +32,16 @@ index 582416f..47e7201 100644 'NOT EXISTS' => ['prefix' => 'FILTER NOT EXISTS {', 'suffix' => '}'], '<' => ['prefix' => '', 'suffix' => ''], '>' => ['prefix' => '', 'suffix' => ''], -@@ -271,10 +271,6 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn - 'lang' => $lang, +@@ -272,7 +272,7 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn 'column' => $column, ]; -- + - if (!in_array($operator, ['EXISTS', 'NOT EXISTS'])) { -- $this->requiresDefaultPattern = FALSE; -- } ++ if ($operator !== 'NOT EXISTS') { + $this->requiresDefaultPattern = FALSE; + } } - - return $this; -@@ -384,6 +380,7 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn +@@ -384,6 +384,7 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn } $mappings = $this->fieldHandler->getFieldPredicates($entity_type_id, $field, $column); @@ -51,7 +49,7 @@ index 582416f..47e7201 100644 $field_name = $field . '__' . $column; if (count($mappings) === 1) { $this->fieldMappings[$field_name] = reset($mappings); -@@ -396,10 +393,10 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn +@@ -396,10 +397,10 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn // loaded by the database. There is no way that in a single request, // the same predicate is found with a single and multiple mappings. // There is no filter per bundle in the query. @@ -64,7 +62,7 @@ index 582416f..47e7201 100644 'operator' => 'IN', ]; } -@@ -445,10 +442,11 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn +@@ -445,10 +446,11 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn $field_name = $condition['field'] . '__' . $condition['column']; $field_predicate = $this->fieldMappings[$field_name]; $condition_string = self::ID_KEY . ' ' . $this->escapePredicate($field_predicate) . ' ' . SparqlArg::toVar($field_name); @@ -77,7 +75,7 @@ index 582416f..47e7201 100644 $this->addConditionFragment($condition_string); } } -@@ -503,8 +501,11 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn +@@ -503,8 +505,11 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn break; case 'EXISTS': @@ -90,7 +88,7 @@ index 582416f..47e7201 100644 break; case 'CONTAINS': -@@ -589,18 +590,66 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn +@@ -589,18 +594,66 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn } /** From 1c0d87c1fca190dd2db01c251ec75f42709d5062 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 14 Apr 2021 09:39:15 +0300 Subject: [PATCH 8/8] ISAICP-6242: Use module's alpha9 release. --- composer.json | 4 +- composer.lock | 50 +++-- ...ql_entity_storage_support_not_exists.patch | 193 ------------------ 3 files changed, 39 insertions(+), 208 deletions(-) delete mode 100644 resources/patch/sparql_entity_storage_support_not_exists.patch diff --git a/composer.json b/composer.json index 5c67aea0a4..e8142e7788 100644 --- a/composer.json +++ b/composer.json @@ -334,9 +334,7 @@ }, "drupal/sparql_entity_storage": { "Bundle classes support @see https://github.com/ec-europa/sparql_entity_storage/pull/11": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/11.diff", - "Allow routes to filter by bundle @see https://github.com/ec-europa/sparql_entity_storage/pull/32": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/32.diff", - "Allow to pass options to the serializer @see https://github.com/idimopoulos/sparql_entity_storage/pull/3": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/3.diff", - "Support NOT EXISTS query @see https://github.com/idimopoulos/sparql_entity_storage/pull/4": "resources/patch/sparql_entity_storage_support_not_exists.patch" + "Allow routes to filter by bundle @see https://github.com/ec-europa/sparql_entity_storage/pull/32": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/32.diff" }, "drupal/subpathauto": { "Subpaths with redirect not resolved @see https://www.drupal.org/project/subpathauto/issues/3175637": "https://git.drupalcode.org/project/subpathauto/-/merge_requests/1/diffs.diff" diff --git a/composer.lock b/composer.lock index fe7b66e508..0df99ebfb7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8681f0d6358572b41f01b4aebc5520a2", + "content-hash": "07c40765da9245df16dcaad3f2e46c4d", "packages": [ { "name": "SEMICeu/adms-ap_validator", @@ -6596,17 +6596,17 @@ }, { "name": "drupal/sparql_entity_storage", - "version": "1.0.0-alpha8", + "version": "1.0.0-alpha9", "source": { "type": "git", "url": "https://git.drupalcode.org/project/sparql_entity_storage.git", - "reference": "8.x-1.0-alpha8" + "reference": "8.x-1.0-alpha9" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/sparql_entity_storage-8.x-1.0-alpha8.zip", - "reference": "8.x-1.0-alpha8", - "shasum": "813d9e19bf5fb6443b26091af2d4fa3602cf552c" + "url": "https://ftp.drupal.org/files/projects/sparql_entity_storage-8.x-1.0-alpha9.zip", + "reference": "8.x-1.0-alpha9", + "shasum": "c4228b2957dcfe4ff3f47d57375040f83b2b9f0d" }, "require": { "drupal/core": "^8 || ^9", @@ -6614,23 +6614,49 @@ "ml/json-ld": "^1.0" }, "require-dev": { - "minimaxir/big-list-of-naughty-strings": "dev-master" + "composer/installers": "^1.7", + "drupal/coder": "^8.3", + "drupal/core": "^8.9 || ^9.1", + "drupal/core-composer-scaffold": "^9.0", + "mglaman/phpstan-drupal": "^0.12.9", + "mikey179/vfsstream": "^1.6", + "minimaxir/big-list-of-naughty-strings": "dev-master", + "phpspec/prophecy-phpunit": "^1.0 || ^2.0", + "phpstan/phpstan-deprecation-rules": "^0.12.5", + "phpunit/phpunit": "*", + "symfony/phpunit-bridge": "^5.1.4", + "zaporylie/composer-drupal-optimizations": "^1.1" }, "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-1.0-alpha8", - "datestamp": "1602943655", + "version": "8.x-1.0-alpha9", + "datestamp": "1618382134", "security-coverage": { "status": "not-covered", "message": "Project has not opted into security advisory coverage!" } }, + "installer-paths": { + "../testing_site/core": [ + "type:drupal-core" + ], + "../testing_site/modules/{$name}": [ + "type:drupal-module" + ] + }, + "drupal-scaffold": { + "locations": { + "web-root": "../testing_site/" + }, + "file-mapping": { + "[project-root]/.editorconfig": false, + "[project-root]/.gitattributes": false + } + }, "patches_applied": { "Bundle classes support @see https://github.com/ec-europa/sparql_entity_storage/pull/11": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/11.diff", - "Allow routes to filter by bundle @see https://github.com/ec-europa/sparql_entity_storage/pull/32": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/32.diff", - "Allow to pass options to the serializer @see https://github.com/idimopoulos/sparql_entity_storage/pull/3": "https://patch-diff.githubusercontent.com/raw/idimopoulos/sparql_entity_storage/pull/3.diff", - "Support NOT EXISTS query @see https://github.com/idimopoulos/sparql_entity_storage/pull/4": "resources/patch/sparql_entity_storage_support_not_exists.patch" + "Allow routes to filter by bundle @see https://github.com/ec-europa/sparql_entity_storage/pull/32": "https://patch-diff.githubusercontent.com/raw/ec-europa/sparql_entity_storage/pull/32.diff" } }, "autoload": { diff --git a/resources/patch/sparql_entity_storage_support_not_exists.patch b/resources/patch/sparql_entity_storage_support_not_exists.patch deleted file mode 100644 index 6795866a4c..0000000000 --- a/resources/patch/sparql_entity_storage_support_not_exists.patch +++ /dev/null @@ -1,193 +0,0 @@ -diff --git a/src/Entity/Query/Sparql/Query.php b/src/Entity/Query/Sparql/Query.php -index 9215946..3097e69 100644 ---- a/src/Entity/Query/Sparql/Query.php -+++ b/src/Entity/Query/Sparql/Query.php -@@ -355,6 +355,16 @@ class Query extends QueryBase implements SparqlQueryInterface { - if (!in_array($direction, ['ASC', 'DESC'])) { - throw new \RuntimeException('Only "ASC" and "DESC" are allowed as sort order.'); - } -+ -+ // Unlike the normal SQL queries where a column not defined can be used for -+ // sorting if exists in the table, in SPARQL, the sort argument must be -+ // defined in the WHERE clause. Any sort property, therefore, must will be -+ // included with an EXISTS condition. -+ // Also, the $idKey and $bundleKey properties cannot be added as triples as -+ // they cannot be the object of the triple. -+ if (!in_array($field, [$this->idKey, $this->bundleKey])) { -+ $this->exists($field); -+ } - return parent::sort($field, $direction, $langcode); - } - -diff --git a/src/Entity/Query/Sparql/SparqlCondition.php b/src/Entity/Query/Sparql/SparqlCondition.php -index 582416f..cd6de5b 100644 ---- a/src/Entity/Query/Sparql/SparqlCondition.php -+++ b/src/Entity/Query/Sparql/SparqlCondition.php -@@ -70,7 +70,7 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn - 'ENDS WITH' => ['prefix' => 'FILTER(STRENDS(', 'suffix' => '))'], - 'LIKE' => ['prefix' => 'FILTER(CONTAINS(', 'suffix' => '))'], - 'NOT LIKE' => ['prefix' => 'FILTER(!CONTAINS(', 'suffix' => '))'], -- 'EXISTS' => ['prefix' => 'FILTER EXISTS {', 'suffix' => '}'], -+ 'EXISTS' => ['prefix' => '', 'suffix' => ''], - 'NOT EXISTS' => ['prefix' => 'FILTER NOT EXISTS {', 'suffix' => '}'], - '<' => ['prefix' => '', 'suffix' => ''], - '>' => ['prefix' => '', 'suffix' => ''], -@@ -272,7 +272,7 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn - 'column' => $column, - ]; - -- if (!in_array($operator, ['EXISTS', 'NOT EXISTS'])) { -+ if ($operator !== 'NOT EXISTS') { - $this->requiresDefaultPattern = FALSE; - } - } -@@ -384,6 +384,7 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn - } - - $mappings = $this->fieldHandler->getFieldPredicates($entity_type_id, $field, $column); -+ $mappings = array_values(array_unique($mappings)); - $field_name = $field . '__' . $column; - if (count($mappings) === 1) { - $this->fieldMappings[$field_name] = reset($mappings); -@@ -396,10 +397,10 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn - // loaded by the database. There is no way that in a single request, - // the same predicate is found with a single and multiple mappings. - // There is no filter per bundle in the query. -- $this->fieldMappingConditions[] = [ -+ $this->fieldMappingConditions[$field_name] = [ - 'field' => $field, - 'column' => $column, -- 'value' => array_values(array_unique($mappings)), -+ 'value' => $mappings, - 'operator' => 'IN', - ]; - } -@@ -445,10 +446,11 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn - $field_name = $condition['field'] . '__' . $condition['column']; - $field_predicate = $this->fieldMappings[$field_name]; - $condition_string = self::ID_KEY . ' ' . $this->escapePredicate($field_predicate) . ' ' . SparqlArg::toVar($field_name); -+ $this->addConditionFragment($condition_string); - - $condition['value'] = SparqlArg::toResourceUris($condition['value']); - $condition['field'] = $field_predicate; -- $condition_string .= ' . ' . $this->compileValuesFilter($condition); -+ $condition_string = $this->compileValuesFilter($condition); - $this->addConditionFragment($condition_string); - } - } -@@ -503,8 +505,11 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn - break; - - case 'EXISTS': -+ $this->compileExists($condition); -+ break; -+ - case 'NOT EXISTS': -- $this->addConditionFragment($this->compileExists($condition)); -+ $this->compileNotExists($condition); - break; - - case 'CONTAINS': -@@ -589,18 +594,66 @@ class SparqlCondition extends ConditionFundamentals implements SparqlConditionIn - } - - /** -- * Compiles a filter exists (or not exists) condition. -+ * Compiles a filter exists condition. -+ * -+ * Since a triple in SPARQL works just like EXISTS does, for EXISTS we add -+ * any condition missing from the field mapping fragments. - * - * @param array $condition - * An array that contains the 'field', 'value', 'operator' values. -+ */ -+ protected function compileExists(array $condition): void { -+ $field_predicate = $this->fieldMappings[$condition['field']]; -+ $condition_strings = []; -+ $condition_strings[] = self::ID_KEY . ' ' . $this->escapePredicate($field_predicate) . ' ' . SparqlArg::toVar($condition['field']); -+ -+ if (isset($this->fieldMappingConditions[$condition['field']])) { -+ $mapping_condition = $this->fieldMappingConditions[$condition['field']]; -+ $mapping_condition['value'] = SparqlArg::toResourceUris($mapping_condition['value']); -+ $mapping_condition['field'] = $field_predicate; -+ $condition_strings[] = $this->compileValuesFilter($mapping_condition); -+ } -+ -+ foreach ($condition_strings as $condition_string) { -+ if (array_search($condition_string, $this->conditionFragments) === FALSE) { -+ $this->addConditionFragment($condition_string); -+ } -+ } -+ } -+ -+ /** -+ * Compiles a filter not exists condition. - * -- * @return string -- * A condition fragment string. -+ * @param array $condition -+ * An array that contains the 'field', 'value', 'operator' values. - */ -- protected function compileExists(array $condition): string { -+ protected function compileNotExists(array $condition): void { - $prefix = self::$filterOperatorMap[$condition['operator']]['prefix']; - $suffix = self::$filterOperatorMap[$condition['operator']]['suffix']; -- return $prefix . self::ID_KEY . ' ' . $this->escapePredicate($this->fieldMappings[$condition['field']]) . ' ' . SparqlArg::toVar($condition['field']) . $suffix; -+ -+ $field_predicate = $this->fieldMappings[$condition['field']]; -+ $condition_strings = []; -+ $condition_strings[] = self::ID_KEY . ' ' . $this->escapePredicate($field_predicate) . ' ' . SparqlArg::toVar($condition['field']); -+ -+ if (isset($this->fieldMappingConditions[$condition['field']])) { -+ $mapping_condition = $this->fieldMappingConditions[$condition['field']]; -+ $mapping_condition['value'] = SparqlArg::toResourceUris($mapping_condition['value']); -+ $mapping_condition['field'] = $field_predicate; -+ $condition_strings[] = $this->compileValuesFilter($mapping_condition); -+ } -+ -+ foreach ($condition_strings as $condition_string) { -+ $key = array_search($condition_string, $this->conditionFragments); -+ // Since field mapping conditions act also as EXISTS (the triple patterns -+ // MUST exist), remove any pattern added in the mapping conditions so that -+ // only the negative condition below exists. -+ if ($key !== FALSE) { -+ unset($this->conditionFragments[$key]); -+ } -+ } -+ -+ $this->addConditionFragment($prefix . implode(' . ', $condition_strings) . $suffix); -+ - } - - /** -diff --git a/tests/src/Kernel/SparqlEntityQueryTest.php b/tests/src/Kernel/SparqlEntityQueryTest.php -index a8fe4b7..ace3e25 100644 ---- a/tests/src/Kernel/SparqlEntityQueryTest.php -+++ b/tests/src/Kernel/SparqlEntityQueryTest.php -@@ -448,6 +448,26 @@ class SparqlEntityQueryTest extends SparqlKernelTestBase { - ]; - } - -+ /** -+ * Tests the NOT EXISTS operator. -+ */ -+ public function testNotExists() { -+ $entity = SparqlTest::create([ -+ 'id' => 'http://fruit.example.com/not_exists', -+ 'title' => 'fruit title not exists', -+ 'type' => 'fruit', -+ ]); -+ $entity->save(); -+ $this->entities[] = $entity; -+ -+ $results = $this->getQuery() -+ ->condition('type', 'fruit') -+ ->notExists('text') -+ ->execute(); -+ -+ $this->assertNotEmpty($results); -+ } -+ - /** - * Asserts that arrays are identical. - */