diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 5144e65cdc..23beb19b70 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -3,5 +3,6 @@ ## New Features: * Distributed subscriptions: subordinate subscriptions are DELETED when their "father" is deleted * Support for the URL parameter 'csf' in GET /ngsi-ld/v1/csourceRegistrations + * Support for the URL parameter 'orderBy' (must be an attribute) in GET //ngsi-ld/v1/entities, but only if 'local' is set. (This is not NGSI-LD standard. Yet ...) ## Notes diff --git a/src/lib/orionld/context/orionldAttributeExpand.cpp b/src/lib/orionld/context/orionldAttributeExpand.cpp index f5c82f12f4..39c8f99e39 100644 --- a/src/lib/orionld/context/orionldAttributeExpand.cpp +++ b/src/lib/orionld/context/orionldAttributeExpand.cpp @@ -44,21 +44,23 @@ char* orionldAttributeExpand ( OrionldContext* contextP, - char* shortName, + const char* shortName, bool useDefaultUrlIfNotFound, OrionldContextItem** contextItemPP ) { - if (strcmp(shortName, "id") == 0) return shortName; - else if (strcmp(shortName, "@id") == 0) return shortName; - else if (strcmp(shortName, "type") == 0) return shortName; - else if (strcmp(shortName, "@type") == 0) return shortName; - else if (strcmp(shortName, "scope") == 0) return shortName; - else if (strcmp(shortName, "location") == 0) return shortName; - else if (strcmp(shortName, "createdAt") == 0) return shortName; - else if (strcmp(shortName, "modifiedAt") == 0) return shortName; - else if (strcmp(shortName, "observationSpace") == 0) return shortName; - else if (strcmp(shortName, "operationSpace") == 0) return shortName; + char* sName = (char*) shortName; // just to avoid a warning for strcmp + + if (strcmp(sName, "id") == 0) return sName; + else if (strcmp(sName, "@id") == 0) return sName; + else if (strcmp(sName, "type") == 0) return sName; + else if (strcmp(sName, "@type") == 0) return sName; + else if (strcmp(sName, "scope") == 0) return sName; + else if (strcmp(sName, "location") == 0) return sName; + else if (strcmp(sName, "createdAt") == 0) return sName; + else if (strcmp(sName, "modifiedAt") == 0) return sName; + else if (strcmp(sName, "observationSpace") == 0) return sName; + else if (strcmp(sName, "operationSpace") == 0) return sName; #if 1 // FIXME: 'observedAt' as an attribute is not a thing - special treatment only if sub-attribute @@ -70,15 +72,15 @@ char* orionldAttributeExpand // // We should probably forbid an attribute to have the name 'observedAt' // - else if (strcmp(shortName, "observedAt") == 0) + else if (strcmp(sName, "observedAt") == 0) { - orionldContextItemExpand(contextP, shortName, false, contextItemPP); - return shortName; + orionldContextItemExpand(contextP, sName, false, contextItemPP); + return sName; } #endif - if (orionldContextItemAlreadyExpanded(shortName) == true) - return shortName; + if (orionldContextItemAlreadyExpanded(sName) == true) + return sName; - return orionldContextItemExpand(contextP, shortName, useDefaultUrlIfNotFound, contextItemPP); + return orionldContextItemExpand(contextP, sName, useDefaultUrlIfNotFound, contextItemPP); } diff --git a/src/lib/orionld/context/orionldAttributeExpand.h b/src/lib/orionld/context/orionldAttributeExpand.h index 58527822a7..c04ac9f723 100644 --- a/src/lib/orionld/context/orionldAttributeExpand.h +++ b/src/lib/orionld/context/orionldAttributeExpand.h @@ -41,7 +41,7 @@ extern char* orionldAttributeExpand ( OrionldContext* contextP, - char* shortName, + const char* shortName, bool useDefaultUrlIfNotFound, OrionldContextItem** contextItemPP ); diff --git a/src/lib/orionld/entityMaps/entityMapCreate.cpp b/src/lib/orionld/entityMaps/entityMapCreate.cpp index 0137888238..d0eb6a2d0b 100644 --- a/src/lib/orionld/entityMaps/entityMapCreate.cpp +++ b/src/lib/orionld/entityMaps/entityMapCreate.cpp @@ -213,6 +213,7 @@ EntityMap* entityMapCreate(DistOp* distOpList, char* idPattern, QNode* qNode, Or geoInfoP, NULL, geojsonGeometryLongName, + orionldState.uriParams.orderBy, true, false); diff --git a/src/lib/orionld/mhd/mhdConnectionInit.cpp b/src/lib/orionld/mhd/mhdConnectionInit.cpp index fa37cbd6e9..2cdd370ce3 100644 --- a/src/lib/orionld/mhd/mhdConnectionInit.cpp +++ b/src/lib/orionld/mhd/mhdConnectionInit.cpp @@ -1174,7 +1174,7 @@ MHD_Result mhdConnectionInit // 2. NGSI-LD requests don't support the broker to be started with -noCache if (noCache == true) { - orionldError(OrionldBadRequestData, "Not Implemented", "Running without Subscription Cache is not implemented for NGSI-LD requests", 501); + orionldError(OrionldOperationNotSupported, "Not Implemented", "Running without Subscription Cache is not implemented for NGSI-LD requests", 501); return MHD_YES; } diff --git a/src/lib/orionld/mhd/mhdConnectionTreat.cpp b/src/lib/orionld/mhd/mhdConnectionTreat.cpp index dff779e31a..2b65ab772b 100644 --- a/src/lib/orionld/mhd/mhdConnectionTreat.cpp +++ b/src/lib/orionld/mhd/mhdConnectionTreat.cpp @@ -438,6 +438,10 @@ char* pCheckLinkHeader(char* link) +// ----------------------------------------------------------------------------- +// +// linkGet - +// static bool linkGet(const char* link) { // @@ -1095,6 +1099,12 @@ MHD_Result mhdConnectionTreat(void) orionldError(OrionldBadRequestData, "Unsupported URI parameter", detail, 400); goto respond; } + + if ((orionldState.uriParams.orderBy != NULL) && (orionldState.uriParams.local == false)) + { + orionldError(OrionldOperationNotSupported, "Not supported URL parameter", "The URL parameter 'orderBy' only works if also 'local' is set", 501); + goto respond; + } } // diff --git a/src/lib/orionld/mongoc/mongocEntitiesQuery.cpp b/src/lib/orionld/mongoc/mongocEntitiesQuery.cpp index 653ed9954b..5904264812 100644 --- a/src/lib/orionld/mongoc/mongocEntitiesQuery.cpp +++ b/src/lib/orionld/mongoc/mongocEntitiesQuery.cpp @@ -40,6 +40,7 @@ extern "C" #include "orionld/common/orionldError.h" // orionldError #include "orionld/common/dotForEq.h" // dotForEq #include "orionld/q/qTreeToBson.h" // qTreeToBson +#include "orionld/context/orionldAttributeExpand.h" // orionldAttributeExpand #include "orionld/mongoc/mongocWriteLog.h" // MONGOC_RLOG - FIXME: change name to mongocLog.h #include "orionld/mongoc/mongocConnectionGet.h" // mongocConnectionGet #include "orionld/mongoc/mongocKjTreeToBson.h" // mongocKjTreeToBson @@ -661,6 +662,7 @@ KjNode* mongocEntitiesQuery OrionldGeoInfo* geoInfoP, int64_t* countP, const char* geojsonGeometry, + const char* orderBy, bool onlyIds, bool onlyIdAndType ) @@ -696,7 +698,26 @@ KjNode* mongocEntitiesQuery bson_init(&sortDoc); - bson_append_int32(&sortDoc, "creDate", 7, 1); + if (orderBy == NULL) + bson_append_int32(&sortDoc, "creDate", 7, 1); + else + { + char* longName = orionldAttributeExpand(orionldState.contextP, orderBy, true, NULL); + int len = strlen(longName) + 14; + char* path = kaAlloc(&orionldState.kalloc, len); + + len = snprintf(path, len - 1, "attrs.%s.value", longName); + + // Replacing dots for '=' - including the last one (should not be replaced) + dotForEq(&path[6]); + + // Reversing the '=' of ".value" to a dot + path[len - 6] = '.'; + + LM_T(LmtMongoc, ("Ordering by '%s' (%d letters)", path, len)); + bson_append_int32(&sortDoc, path, len, 1); + } + bson_append_int32(&sortDoc, "_id.id", 6, 1); bson_append_document(&options, "sort", 4, &sortDoc); bson_destroy(&sortDoc); diff --git a/src/lib/orionld/mongoc/mongocEntitiesQuery.h b/src/lib/orionld/mongoc/mongocEntitiesQuery.h index a75e06213f..619bcae4f5 100644 --- a/src/lib/orionld/mongoc/mongocEntitiesQuery.h +++ b/src/lib/orionld/mongoc/mongocEntitiesQuery.h @@ -50,6 +50,7 @@ extern KjNode* mongocEntitiesQuery OrionldGeoInfo* geoInfoP, int64_t* countP, const char* geojsonGeometry, + const char* orderBy, bool onlyIds, bool onlyIdAndType ); diff --git a/src/lib/orionld/payloadCheck/pcheckInformationItem.cpp b/src/lib/orionld/payloadCheck/pcheckInformationItem.cpp index 0c7ac1a2f1..b9626ebc82 100644 --- a/src/lib/orionld/payloadCheck/pcheckInformationItem.cpp +++ b/src/lib/orionld/payloadCheck/pcheckInformationItem.cpp @@ -307,7 +307,7 @@ bool pCheckOverlappingEntities(KjNode* entitiesP, KjNode* propertiesP, KjNode* r if (entitiesP == NULL) { - KjNode* entityArray = mongocEntitiesQuery(NULL, NULL, NULL, &attrsV, NULL, NULL, NULL, NULL, false, false); + KjNode* entityArray = mongocEntitiesQuery(NULL, NULL, NULL, &attrsV, NULL, NULL, NULL, NULL, orionldState.uriParams.orderBy, false, false); if ((entityArray != NULL) && (entityArray->value.firstChildP != NULL)) { KjNode* _idP = kjLookup(entityArray->value.firstChildP, "_id"); @@ -350,7 +350,7 @@ bool pCheckOverlappingEntities(KjNode* entitiesP, KjNode* propertiesP, KjNode* r entityTypeList.array = entityTypeArray; entityTypeArray[0] = entityType; - KjNode* entityArray = mongocEntitiesQuery(&entityTypeList, NULL, entityIdPattern, &attrsV, NULL, NULL, NULL, NULL, false, false); + KjNode* entityArray = mongocEntitiesQuery(&entityTypeList, NULL, entityIdPattern, &attrsV, NULL, NULL, NULL, NULL, NULL, false, false); if ((entityArray != NULL) && (entityArray->value.firstChildP != NULL)) { KjNode* _idP = kjLookup(entityArray->value.firstChildP, "_id"); diff --git a/src/lib/orionld/service/orionldServiceInit.cpp b/src/lib/orionld/service/orionldServiceInit.cpp index 79999a7107..e2a3dc5527 100644 --- a/src/lib/orionld/service/orionldServiceInit.cpp +++ b/src/lib/orionld/service/orionldServiceInit.cpp @@ -244,6 +244,7 @@ static void restServicePrepare(OrionLdRestService* serviceP, OrionLdRestServiceS serviceP->uriParams |= ORIONLD_URIPARAM_LOCAL; serviceP->uriParams |= ORIONLD_URIPARAM_ONLYIDS; serviceP->uriParams |= ORIONLD_URIPARAM_ENTITYMAP; + serviceP->uriParams |= ORIONLD_URIPARAM_ORDERBY; } else if (serviceP->serviceRoutine == orionldGetEntity) { diff --git a/src/lib/orionld/serviceRoutines/orionldDeleteEntities.cpp b/src/lib/orionld/serviceRoutines/orionldDeleteEntities.cpp index e2cb190359..8ca570adc6 100644 --- a/src/lib/orionld/serviceRoutines/orionldDeleteEntities.cpp +++ b/src/lib/orionld/serviceRoutines/orionldDeleteEntities.cpp @@ -166,6 +166,7 @@ bool orionldDeleteEntities(void) &geoInfo, &count, geojsonGeometryLongName, + NULL, false, true); // diff --git a/src/lib/orionld/serviceRoutines/orionldGetEntitiesLocal.cpp b/src/lib/orionld/serviceRoutines/orionldGetEntitiesLocal.cpp index 7cb40b8bd1..6539321c68 100644 --- a/src/lib/orionld/serviceRoutines/orionldGetEntitiesLocal.cpp +++ b/src/lib/orionld/serviceRoutines/orionldGetEntitiesLocal.cpp @@ -190,6 +190,7 @@ bool orionldGetEntitiesLocal geoInfoP, &count, geojsonGeometryLongName, + orionldState.uriParams.orderBy, onlyIds, false); diff --git a/src/lib/orionld/serviceRoutines/orionldPostBatchCreate.cpp b/src/lib/orionld/serviceRoutines/orionldPostBatchCreate.cpp index 8a55682324..ec6fe0acf1 100644 --- a/src/lib/orionld/serviceRoutines/orionldPostBatchCreate.cpp +++ b/src/lib/orionld/serviceRoutines/orionldPostBatchCreate.cpp @@ -102,7 +102,7 @@ bool orionldPostBatchCreate(void) // // FIXME: Use simpler mongoc function - we only need to know whether the entities exist or not // - KjNode* dbEntityArray = mongocEntitiesQuery(NULL, &eIdArray, NULL, NULL, NULL, NULL, NULL, NULL, false, false); + KjNode* dbEntityArray = mongocEntitiesQuery(NULL, &eIdArray, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false); if (dbEntityArray == NULL) { orionldError(OrionldInternalError, "Database Error", "error querying the database for entities", 500); diff --git a/src/lib/orionld/serviceRoutines/orionldPostBatchUpdate.cpp b/src/lib/orionld/serviceRoutines/orionldPostBatchUpdate.cpp index 95f39c0473..1e93af75d8 100644 --- a/src/lib/orionld/serviceRoutines/orionldPostBatchUpdate.cpp +++ b/src/lib/orionld/serviceRoutines/orionldPostBatchUpdate.cpp @@ -108,7 +108,7 @@ bool orionldPostBatchUpdate(void) // // The entity id array is ready - time to query mongo // - KjNode* dbEntityArray = mongocEntitiesQuery(NULL, &eIdArray, NULL, NULL, NULL, NULL, NULL, NULL, false, false); + KjNode* dbEntityArray = mongocEntitiesQuery(NULL, &eIdArray, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false); if (dbEntityArray == NULL) { orionldError(OrionldInternalError, "Database Error", "error querying the database for entities", 500); diff --git a/src/lib/orionld/serviceRoutines/orionldPostBatchUpsert.cpp b/src/lib/orionld/serviceRoutines/orionldPostBatchUpsert.cpp index 047085a2d9..f918b7fef3 100644 --- a/src/lib/orionld/serviceRoutines/orionldPostBatchUpsert.cpp +++ b/src/lib/orionld/serviceRoutines/orionldPostBatchUpsert.cpp @@ -173,7 +173,7 @@ bool orionldPostBatchUpsert(void) // orionldState.uriParams.limit = noOfEntities; orionldState.uriParams.offset = 0; - KjNode* dbEntityArray = mongocEntitiesQuery(NULL, &eIdArray, NULL, NULL, NULL, NULL, NULL, NULL, false, false); + KjNode* dbEntityArray = mongocEntitiesQuery(NULL, &eIdArray, NULL, NULL, NULL, NULL, NULL, NULL, orionldState.uriParams.orderBy, false, false); if (dbEntityArray == NULL) { orionldError(OrionldInternalError, "Database Error", "error querying the database for entities", 500); diff --git a/src/lib/orionld/types/OrionLdRestService.h b/src/lib/orionld/types/OrionLdRestService.h index 40fdd3bf13..0c3f212157 100644 --- a/src/lib/orionld/types/OrionLdRestService.h +++ b/src/lib/orionld/types/OrionLdRestService.h @@ -157,6 +157,7 @@ typedef struct OrionLdRestServiceSimplifiedVector #define ORIONLD_URIPARAM_FORMAT (UINT64_C(1) << 40) #define ORIONLD_URIPARAM_EXPAND_VALUES (UINT64_C(1) << 41) #define ORIONLD_URIPARAM_KIND (UINT64_C(1) << 42) +#define ORIONLD_URIPARAM_ORDERBY (UINT64_C(1) << 43) diff --git a/test/functionalTest/cases/0000_ngsild/ngsild_orderBy.test b/test/functionalTest/cases/0000_ngsild/ngsild_orderBy.test new file mode 100644 index 0000000000..16919ae788 --- /dev/null +++ b/test/functionalTest/cases/0000_ngsild/ngsild_orderBy.test @@ -0,0 +1,234 @@ +# Copyright 2024 FIWARE Foundation e.V. +# +# This file is part of Orion-LD Context Broker. +# +# Orion-LD Context Broker is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# Orion-LD Context Broker is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero +# General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Orion-LD Context Broker. If not, see http://www.gnu.org/licenses/. +# +# For those usages not covered by this license please contact with +# orionld at fiware dot org + +# VALGRIND_READY - to mark the test ready for valgrindTestSuite.sh + +--NAME-- +GET /entities with ?orderBy=xxx for local query + +--SHELL-INIT-- +dbInit CB +orionldStart CB -experimental + +typeset -i eNo +eNo=50 + +while [ $eNo -ge 0 ] +do + eId=$(printf "urn:ngsi-ld:entities:E%02d" $eNo) + + payload='{ + "id": "'$eId'", + "type": "T'$eNo'", + "relevance": '$eNo' + }' + orionCurl --url /ngsi-ld/v1/entities --payload "$payload" | grep 'Location:' + eNo=$eNo-1 +done + +--SHELL-- + +# +# 01. GET entities ordered by 'relevance' +# 02. GET entities ordered by 'relevance', offset 5 +# 03. GET entities without specific order (createdAt is default) +# + +echo "01. GET entities ordered by 'relevance'" +echo "=======================================" +orionCurl --url "/ngsi-ld/v1/entities?local=true&orderBy=relevance&limit=5" +echo +echo + + +echo "02. GET entities ordered by 'relevance', offset 20" +echo "==================================================" +orionCurl --url "/ngsi-ld/v1/entities?local=true&orderBy=relevance&offset=5&limit=5" +echo +echo + + +echo "03. GET entities without specific order (createdAt is default)" +echo "==============================================================" +orionCurl --url "/ngsi-ld/v1/entities?local=true&limit=5" +echo +echo + + +--REGEXPECT-- +01. GET entities ordered by 'relevance' +======================================= +HTTP/1.1 200 OK +Content-Length: 441 +Content-Type: application/json +Date: REGEX(.*) +Link: