Skip to content

Commit

Permalink
[EXPERIMENTAL] Partially adress issue #408. Probably break getFacets …
Browse files Browse the repository at this point in the history
…so needs to be checked before release
  • Loading branch information
jjrom committed May 14, 2024
1 parent efd2ad3 commit 9eb4601
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 41 deletions.
48 changes: 26 additions & 22 deletions app/resto/core/addons/STAC.php
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@ private function load($params = array())
}
}

// Hashtags
// Hashtags or catalogs
elseif ($this->segments[0] === 'hashtags' || $this->segments[0] === 'catalogs') {
$this->setCatalogsLinksFromFacet($params);
}
Expand Down Expand Up @@ -1275,11 +1275,9 @@ private function setCatalogsLinksFromFacet($params)
// Default segments structure starts with 'resto:classifications/xxxx' - so start at position 2
$leafPosition = 2;

$where = 'type=$1';
$whereValues = array(
$leafValue
);

$where = array();
$whereValues = array();

// Hashtags special case
if ($this->segments[0] === 'hashtags' || $this->segments[0] === 'catalogs') {

Expand All @@ -1288,26 +1286,25 @@ private function setCatalogsLinksFromFacet($params)
// In database keyword is 'hashtag' not 'hashtags'
if ($nbOfSegments === 1) {
$leafValue = substr($this->segments[0], 0, -1);
$whereValues = array(
$leafValue
);
$whereValues[] = $leafValue;
$where[] = 'type=$' . count($whereValues);
}

// Hack for catalog - force a hierarchy
if ($this->segments[0] === 'catalogs') {
$where = $where . ' AND pid=$2';
$whereValues[] = 'root';
}
}


// Hack for catalog - force a hierarchy
if ($this->segments[0] === 'catalogs') {
$whereValues[] = 'root';
$where[] = 'public.normalize(pid)=public.normalize($' . count($whereValues). ')';
}

// Hack for landcover...
elseif ($this->segments[1] === 'landcover') {

if ($nbOfSegments === 2) {
$where = 'type LIKE $1';
$whereValues = array(
'landcover:%'
);
$whereValues[] = 'landcover:%';
$where[] = 'type LIKE $' . count($whereValues);
}
}

Expand All @@ -1325,16 +1322,23 @@ private function setCatalogsLinksFromFacet($params)
// First get type or pid
// [TODO] Return /search items instead of child for high number of results ?
// $results = $this->context->dbDriver->pQuery('SELECT id, value, isleaf, sum(counter) as matched FROM ' . $this->context->dbDriver->targetSchema . '.facet WHERE ' . ($nbOfSegments === 1 ? 'type LIKE $1' : 'pid=$1' ) . ' GROUP BY id,value,isleaf ORDER BY matched DESC', array(
$results = $this->context->dbDriver->pQuery('SELECT id, value, pid, isleaf, sum(counter) as matched FROM ' . $this->context->dbDriver->targetSchema . '.facet WHERE ' . ($nbOfSegments === $leafPosition ? $where : 'pid=$1') . ' GROUP BY id, value, pid, isleaf ORDER BY value ASC', $nbOfSegments === $leafPosition ? $whereValues : array($whereValues[0]));
//$results = $this->context->dbDriver->pQuery('SELECT id, value, pid, isleaf, sum(counter) as matched FROM ' . $this->context->dbDriver->targetSchema . '.facet WHERE ' . ($nbOfSegments === $leafPosition ? $where : 'public.normalize(pid)=public.normalize($1)') . ' GROUP BY id, value, pid, isleaf ORDER BY value ASC', $nbOfSegments === $leafPosition ? $whereValues : array($whereValues[0]));
$results = $this->context->dbDriver->pQuery('SELECT id, value, pid, isleaf, sum(counter) as matched FROM ' . $this->context->dbDriver->targetSchema . '.facet' . (count($where) > 0 ? ' WHERE ' . join(' AND ', $where) : '') . ' GROUP BY id, value, pid, isleaf ORDER BY value ASC', $whereValues);

if (!$results) {
throw new Exception();
throw new Exception('Error', 500);
}

// No Results - either a wrong path or a leaf facet (except for hashtag)
if (pg_num_rows($results) === 0) {

// Catalog does not exist
if ($this->segments[0] === 'catalogs') {
throw new Exception('Not Found', 404);
}

//return $this->setItemsLinks($title, $leafValue);
if (!in_array($leafValue, array('hashtag')) && $nbOfSegments > 1) {
if ( !in_array($leafValue, array('hashtag')) && $nbOfSegments > 1 ) {
return $this->setFeatureCollection($leafValue, $params);
}
}
Expand Down Expand Up @@ -1386,7 +1390,7 @@ private function setCatalogsLinksFromFacet($params)
}
}
} catch (Exception $e) {
// Keep going
RestoLogUtil::httpError($e->getCode(), $e->getMessage());
}
}

Expand Down
32 changes: 19 additions & 13 deletions app/resto/core/addons/STACCatalog.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,7 @@ public function addCatalog($params, $body)
$body['links'] = array();
}

$this->getChilds($body['links']);

return $this->storeCatalogAsFacet($body, $params['pid'] ?? null, $childs);
return $this->storeCatalogAsFacet($body, $params['pid'] ?? null, $this->getChilds($body['links']));

}

Expand Down Expand Up @@ -334,7 +332,7 @@ private function catalogExists($catalogId, $parentId, $collectionId)
if ( isset($collectionId) ) {
$params['collection'] = $collectionId;
}
$facets = (new FacetsFunctions($this->context->dbDriver))->getFacets(array('id' => $facetId));
$facets = (new FacetsFunctions($this->context->dbDriver))->getFacets($params);

return !empty($facets);

Expand Down Expand Up @@ -440,10 +438,13 @@ private function resolveLink($link)
try {
$curl = new Curly();
$resolved = json_decode($curl->get($link['href']), true);
if ( isset($resolved['ErrorCode']) ) {
throw new Exception();
}
$curl->close();
} catch (Exception $e) {
$curl->close();
return RestoLogUtil::httpError(400, 'Invalid link with href ' . $link['href']);
return null;
}

return $resolved;
Expand All @@ -463,8 +464,9 @@ private function parentIdFromLinks($links)
for ($i = 0, $ii = count($links); $i < $ii; $i++ ) {
if ( isset($links[$i]['rel']) && $links[$i]['rel'] === 'parent' ) {
$parent = $this->resolveLink($links[$i]);
// To avoid egg and chicken issue, if the parent is not valid, we force a 'root' parent
if ( !isset($parent) || !isset($parent['id']) || !isset($parent['type']) ) {
return RestoLogUtil::httpError(400, 'Invalid parent link with href ' . $links[$i]['href']);
break;
}
return str_starts_with($parent['id'], $parent['type'] . ':') ? $parent['id'] : $parent['type'] . ':' . $parent['id'];
}
Expand Down Expand Up @@ -501,25 +503,29 @@ private function getChilds($links)

$resolved = $this->resolveLink($links[$i]);

if ( ! isset($resolved) || ! isset($resolved['id']) ) {
return RestoLogUtil::httpError(400, 'Invalid link with href ' . $link['href']);
if ( ! isset($resolved) ) {
return RestoLogUtil::httpError(400, 'Child with href ' . $links[$i]['href'] . ' does not exist in database');
}

if ( ! isset($resolved['type']) || !in_array($resolved['type'], array('catalog', 'collection')) ) {
return RestoLogUtil::httpError(400, 'Invalid type in link with href ' . $link['href']);
if ( ! isset($resolved['id']) ) {
return RestoLogUtil::httpError(400, 'Link with href ' . $links[$i]['href'] . ' is missing mandatory id');
}

if ( ! isset($resolved['type']) || !in_array($resolved['type'], array('Catalog', 'Collection')) ) {
return RestoLogUtil::httpError(400, 'Link with href ' . $links[$i]['href'] . ' has an invalid type');
}

if ( $resolved['type'] === 'catalog' ) {
if ( $resolved['type'] === 'Catalog' ) {
$catalogId = str_starts_with($resolved['id'], $this->prefix) ? $resolved['id'] : $this->prefix . $resolved['id'];
if ( !$this->catalogExists($catalogId) ) {
if ( !$this->catalogExists($catalogId, null, null) ) {
return RestoLogUtil::httpError(404, 'Child catalog ' . $catalogId . ' not found');
}
$childs[] = array(
'id' => $catalogId,
'type' => $resolved['type'],
);
}
else if ( $resolved['type'] === 'collection' ) {
else if ( $resolved['type'] === 'Collection' ) {
$collectionsFunctions = new CollectionsFunctions($this->context->dbDriver);
if ( !$collectionsFunctions->collectionExists($collectionsFunctions->aliasToCollectionId($resolved['id'])) ) {
return RestoLogUtil::httpError(404, 'Child collection ' . $resolved['id'] . ' not found');
Expand Down
4 changes: 2 additions & 2 deletions app/resto/core/dbfunctions/CollectionsFunctions.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public function removeCollection($collection)
*/
if ($this->collectionExists($collection->id)) {
$this->dbDriver->query('ROLLBACK');
throw new Exception(500, 'Cannot delete collection ' . $collection->id);
throw new Exception('Cannot delete collection ' . $collection->id, 500);
}

} catch (Exception $e) {
Expand Down Expand Up @@ -198,7 +198,7 @@ public function storeCollection($collection, $rights)
*/
if (! $this->collectionExists($collection->id)) {
$this->dbDriver->query('ROLLBACK');
throw new Exception(500, 'Missing collection');
throw new Exception('Missing collection', 500);
}

} catch (Exception $e) {
Expand Down
2 changes: 1 addition & 1 deletion app/resto/core/dbfunctions/FacetsFunctions.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public function storeFacets($facets, $userid, $collectionId = '*')
*
* [IMPORTANT] UPSERT with check on parentId only if $facetElement['parentId'] is set
*/
$insert = 'INSERT INTO ' . $this->dbDriver->targetSchema . '.facet (id, collection, value, type, pid, owner, description, created, counter, isleaf) SELECT public.normalize($1),$2,$3,$4,public.normalize($5),$6,$7,now(),$8,$9';
$insert = 'INSERT INTO ' . $this->dbDriver->targetSchema . '.facet (id, collection, value, type, pid, owner, description, created, counter, isleaf) SELECT $1,$2,$3,$4,$5,$6,$7,now(),$8,$9';
$upsert = 'UPDATE ' . $this->dbDriver->targetSchema . '.facet SET counter=' .(isset($facetElement['counter']) ? 'counter' : 'counter+1') . ' WHERE public.normalize(id)=public.normalize($1) AND public.normalize(collection)=public.normalize($2)' . (isset($facetElement['parentId']) ? ' AND public.normalize(pid)=public.normalize($5)' : '');
$this->dbDriver->pQuery('WITH upsert AS (' . $upsert . ' RETURNING *) ' . $insert . ' WHERE NOT EXISTS (SELECT * FROM upsert)', array(
$facetElement['id'],
Expand Down
4 changes: 2 additions & 2 deletions app/resto/core/utils/STACUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public function getFacetsCount($minMatch)
}

try {
$results = $this->context->dbDriver->query('SELECT split_part(type, \':\', 1) as type, sum(counter) as matched FROM ' . $this->context->dbDriver->targetSchema . '.facet WHERE pid=\'root\' GROUP BY split_part(type, \':\', 1) ORDER BY type ASC');
$results = $this->context->dbDriver->query('SELECT split_part(type, \':\', 1) as type, sum(counter) as matched FROM ' . $this->context->dbDriver->targetSchema . '.facet WHERE public.normalize(pid)=\'root\' GROUP BY split_part(type, \':\', 1) ORDER BY type ASC');

if (!$results) {
throw new Exception();
Expand Down Expand Up @@ -280,7 +280,7 @@ private function getCatalogChilds()

try {

$results = $this->context->dbDriver->query('SELECT id, value, pid, isleaf, sum(counter) as matched FROM resto.facet WHERE type=\'catalog\' AND pid=\'root\' GROUP BY id, value, pid, isleaf ORDER BY value ASC');
$results = $this->context->dbDriver->query('SELECT id, value, pid, isleaf, sum(counter) as matched FROM resto.facet WHERE type=\'catalog\' AND public.normalize(pid)=\'root\' GROUP BY id, value, pid, isleaf ORDER BY value ASC');

if (!$results) {
throw new Exception();
Expand Down
11 changes: 11 additions & 0 deletions docs/COLLECTIONS_AND_CATALOGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ Then get the feature :
# Create a catalog - user with the "createCatalog" right can create a catalog
curl -X POST -d@examples/catalogs/dummyCatalog.json "http://admin:admin@localhost:5252/catalogs"

### Create a catalog with childs

# This will raise an error because catalog' childs does not exist. They must be created first
curl -X POST -d@examples/catalogs/dummyCatalogWithChilds.json "http://admin:admin@localhost:5252/catalogs"

# Good way : ingest childs then parent
curl -X POST -d@examples/catalogs/dummyCatalogChild1.json "http://admin:admin@localhost:5252/catalogs"
curl -X POST -d@examples/catalogs/dummyCatalogChild2.json "http://admin:admin@localhost:5252/catalogs"
curl -X POST -d@examples/catalogs/dummyCatalogWithChilds.json "http://admin:admin@localhost:5252/catalogs"


### Update a catalog
Only "title", "description" and "owner" properties can be updated

Expand Down
3 changes: 2 additions & 1 deletion examples/catalogs/dummyCatalog.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"id": "dummyCatalog",
"title": "Dummy Catalog",
"type": "Catalog",
"description":"There is nothing to say on this catalog",
"stac_version":"1.0.0",
"links":{}
"links":[]
}
8 changes: 8 additions & 0 deletions examples/catalogs/dummyCatalogChild1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "dummyCatalogChild1",
"type": "Catalog",
"title": "Dummy Catalog child 1",
"description":"I'm a child of dummyCatalog",
"stac_version":"1.0.0",
"links":[]
}
8 changes: 8 additions & 0 deletions examples/catalogs/dummyCatalogChild2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "dummyCatalogChild2",
"type": "Catalog",
"title": "Dummy Catalog child 2",
"description":"I'm a child of dummyCatalog",
"stac_version":"1.0.0",
"links":[]
}
17 changes: 17 additions & 0 deletions examples/catalogs/dummyCatalogWithChilds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "dummyCatalogWithChilds",
"title": "Dummy Catalog with childs",
"type": "Catalog",
"description":"This catalog has childs - the must exist in the database before adding it otherwise it will raise an error",
"stac_version":"1.0.0",
"links":[
{
"rel":"child",
"href":"http://host.docker.internal:5252/catalogs/catalogs/catalog:dummyCatalogChild1"
},
{
"rel":"child",
"href":"http://host.docker.internal:5252/catalogs/catalogs/catalog:dummyCatalogChild2"
}
]
}
22 changes: 22 additions & 0 deletions examples/catalogs/dummyCatalogWithChildsInvalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"id": "dummyCatalog",
"title": "Dummy Catalog",
"type": "Catalog",
"description":"There is nothing to say on this catalog",
"stac_version":"1.0.0",
"links":[
{
"rel":"child",
"href":"http://host.docker.internal:5252/catalogs/catalogs/catalog:dummyCatalogChild1"
},
{
"rel":"child",
"href":"http://host.docker.internal:5252/catalogs/catalogs/catalog:dummyCatalogChild2"
},
{
"rel":"item",
"href":"http://host.docker.internal:5252/collections/dummy/xxxx",
"title":"Catalog can only have child not item/items"
}
]
}

0 comments on commit 9eb4601

Please sign in to comment.