diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 283c45d0ae..4b11ebf46d 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1,6 +1,7 @@ -Fixed Issue: - * #1535 No matching with 'q' when matching subscriptions for deletion of an entity +## Fixed Issues: + * #1535: No matching with 'q' when matching subscriptions for deletion of an entity + * #1542: Array Reduction (arrays of one single element are "flattened" to that very element) -New Features: +## New Features: * Support for VocabularyProperty * Support for the new URL parameter "format" for output formats (normalized, concise, simplified) diff --git a/scripts/requirements.txt b/scripts/requirements.txt index bc18e8eee5..ebd8839301 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -24,3 +24,4 @@ Flask==2.0.3 pyOpenSSL==22.0.0 paho-mqtt==1.5.1 +Werkzeug==2.3.6 diff --git a/src/lib/logMsg/traceLevels.h b/src/lib/logMsg/traceLevels.h index 32647a464a..72cf8cee67 100644 --- a/src/lib/logMsg/traceLevels.h +++ b/src/lib/logMsg/traceLevels.h @@ -141,6 +141,7 @@ typedef enum TraceLevels LmtRegex, // Regular expressions - all of them LmtDateTime, // DateTime (ISO8601) conversion LmtMimeType, // MimeType + LmtArrayReduction, // Arrays of only one item are reduced to the item // // Legacy diff --git a/src/lib/orionld/payloadCheck/pCheckAttribute.cpp b/src/lib/orionld/payloadCheck/pCheckAttribute.cpp index 997bc0e2b4..e29561f0a8 100644 --- a/src/lib/orionld/payloadCheck/pCheckAttribute.cpp +++ b/src/lib/orionld/payloadCheck/pCheckAttribute.cpp @@ -27,6 +27,7 @@ extern "C" { +#include "kbase/kMacros.h" // K_FT #include "kalloc/kaStrdup.h" // kaStrdup #include "kjson/KjNode.h" // KjNode #include "kjson/kjLookup.h" // kjLookup @@ -105,6 +106,33 @@ static const char* attrTypeChangeTitle(OrionldAttributeType oldType, OrionldAttr +// ----------------------------------------------------------------------------- +// +// arrayReduce - +// +static void arrayReduce(KjNode* valueP) +{ + if (valueP->type != KjArray) + return; + + if (valueP->value.firstChildP == NULL) + return; + + if (valueP->value.firstChildP->next != NULL) + return; + + // It's an array with one single item + KjNode* itemP = valueP->value.firstChildP; + + valueP->type = itemP->type; + valueP->value = itemP->value; + valueP->lastChild = itemP->lastChild; + + LM_T(LmtArrayReduction, ("Reduced an array of one single item to a JSON %s", kjValueType(valueP->type))); +} + + + // ----------------------------------------------------------------------------- // // pCheckTypeFromContext - @@ -166,24 +194,37 @@ static bool pCheckTypeFromContext(KjNode* attrP, OrionldContextItem* attrContext } } else if (strcmp(attrContextInfoP->type, "@set") == 0) + { + LM_T(LmtArrayReduction, ("the type for '%s' in the @context is '@set' - arrayReduction is set to FALSE", attrP->name)); arrayReduction = false; + } else if (strcmp(attrContextInfoP->type, "@list") == 0) + { + LM_T(LmtArrayReduction, ("the type for '%s' in the @context is '@list' - arrayReduction is set to FALSE", attrP->name)); arrayReduction = false; + } } // // Array Reduction // + LM_T(LmtArrayReduction, ("Attribute: '%s' - arrayReduction: %s, type '%s'", attrP->name, K_FT(arrayReduction), kjValueType(attrP->type))); + if ((attrP->type == KjArray) && (arrayReduction == true)) { + LM_T(LmtArrayReduction, ("The value of '%s' is an array and arrayReduction is ON", attrP->name)); if ((attrP->value.firstChildP != NULL) && (attrP->value.firstChildP->next == NULL)) { + LM_T(LmtArrayReduction, ("'%s' is an array of one single element => arrayReduction is PERFORMED", attrP->name)); + KjNode* arrayItemP = attrP->value.firstChildP; attrP->type = arrayItemP->type; attrP->value = arrayItemP->value; attrP->lastChild = arrayItemP->lastChild; // Might be an array or object inside the array ... } + else + LM_T(LmtArrayReduction, ("'%s' is an array of zero od +1 elements => arrayReduction is NOT performed", attrP->name)); } return true; @@ -336,7 +377,7 @@ inline bool pCheckAttributeArray valueP->lastChild = attrP->lastChild; pCheckAttributeTransform(attrP, "Property", valueP); - + arrayReduce(valueP); return true; } @@ -1022,14 +1063,23 @@ static bool pCheckAttributeObject if (isGeoJsonValue(valueP) == true) attributeType = GeoProperty; else + { attributeType = Property; + arrayReduce(valueP); + } } else if (objectP != NULL) + { attributeType = Relationship; + arrayReduce(objectP); + } else if (languageMapP != NULL) attributeType = LanguageProperty; else if (vocabP != NULL) + { attributeType = VocabularyProperty; + arrayReduce(vocabP); + } else { // If new attribute and no value field at all - error diff --git a/test/functionalTest/cases/0000_ngsild/ngsild_PATCH_Entity-with-Relationship-array.test b/test/functionalTest/cases/0000_ngsild/ngsild_PATCH_Entity-with-Relationship-array.test index 55e6a29ac3..60937cad04 100644 --- a/test/functionalTest/cases/0000_ngsild/ngsild_PATCH_Entity-with-Relationship-array.test +++ b/test/functionalTest/cases/0000_ngsild/ngsild_PATCH_Entity-with-Relationship-array.test @@ -95,7 +95,7 @@ Location: /ngsi-ld/v1/entities/urn:E1 02. GET 'urn:E1' ================ HTTP/1.1 200 OK -Content-Length: 97 +Content-Length: 95 Content-Type: application/json Date: REGEX(.*) Link: see nothing -# 09. Query entities with q=V1==abc&expandValues=V1,V2 => see urn:E1 -# 10. Query entities with q=V2==id&expandValues=V1,V2 => see urn:E2 +# 08. Dump/Reset accumulator, see urn:E1+V1 and urn:E2+V2 +# +# 09. GET urn:E2 in simplified format +# 10. Query entities with q=V1==abc => see nothing +# 11. Query entities with q=V1==abc&expandValues=V1,V2 => see urn:E1 +# 12. Query entities with q=V2==id&expandValues=V1,V2 => see urn:E2 # echo '01. Create a subscription on entity type T' @@ -93,35 +97,61 @@ echo echo -echo '04. GET urn:E1 to see V1' +echo '04. Create an entity urn:E3, type T, with a VocabularyProperty V3: [ "test" ] - array of one item - flattened to String' +echo '=======================================================================================================================' +payload='{ + "id": "urn:E3", + "type": "T", + "V2": { "vocab": [ "test" ] } +}' +orionCurl --url /ngsi-ld/v1/entities --payload "$payload" +echo +echo + + +echo '05. GET urn:E1 to see V1' echo '========================' orionCurl --url /ngsi-ld/v1/entities/urn:E1 echo echo -echo '04b. See urn:E1 in the DB - see "abc" expanded' +echo '05b. See urn:E1 in the DB - see "abc" expanded' echo '==============================================' mongoCmd2 ftest 'db.entities.findOne({"_id.id": "urn:E1"})' echo echo -echo '05. GET urn:E2 to see V2' +echo '06. GET urn:E2 to see V2' echo '========================' orionCurl --url /ngsi-ld/v1/entities/urn:E2 echo echo -echo '05b. See urn:E2 in the DB - see [ "vocab", "id" ] expanded' +echo '06b. See urn:E2 in the DB - see [ "vocab", "id" ] expanded' echo '==========================================================' mongoCmd2 ftest 'db.entities.findOne({"_id.id": "urn:E2"})' echo echo -echo "06. Dump/Reset accumulator, see urn:E1+V1 and urn:E2+V2" +echo '07. GET urn:E3 to see V3 - see V3 as a string' +echo '=============================================' +orionCurl --url /ngsi-ld/v1/entities/urn:E3 +echo +echo + + +echo '07b. See urn:E3 in the DB - see "test" expanded and V3 as a string, not an array' +echo '================================================================================' +mongoCmd2 ftest 'db.entities.findOne({"_id.id": "urn:E3"})' +echo +echo + + +echo "08. Dump/Reset accumulator, see urn:E1+V1 and urn:E2+V2" echo "=======================================================" sleep .2 accumulatorDump @@ -130,28 +160,28 @@ echo echo -echo "07. GET urn:E2 in simplified format" +echo "09. GET urn:E2 in simplified format" echo "===================================" orionCurl --url /ngsi-ld/v1/entities/urn:E2?options=simplified echo echo -echo "08. Query entities with q=V1==abc => see nothing" +echo "10. Query entities with q=V1==abc => see nothing" echo "================================================" orionCurl --url /ngsi-ld/v1/entities?q=V1==%22abc%22 echo echo -echo "09. Query entities with q=V1==abc&expandValues=V1,V2 => see urn:E1" +echo "11. Query entities with q=V1==abc&expandValues=V1,V2 => see urn:E1" echo "==================================================================" orionCurl --url '/ngsi-ld/v1/entities?q=V1==%22abc%22&expandValues=V1,V2' echo echo -echo "10. Query entities with q=V2==id&expandValues=V1,V2 => see urn:E2" +echo "12. Query entities with q=V2==id&expandValues=V1,V2 => see urn:E2" echo "=================================================================" orionCurl --url '/ngsi-ld/v1/entities?q=V2==%22id%22&expandValues=V1,V2' echo @@ -186,7 +216,16 @@ Location: /ngsi-ld/v1/entities/urn:E2 -04. GET urn:E1 to see V1 +04. Create an entity urn:E3, type T, with a VocabularyProperty V3: [ "test" ] - array of one item - flattened to String +======================================================================================================================= +HTTP/1.1 201 Created +Content-Length: 0 +Date: REGEX(.*) +Location: /ngsi-ld/v1/entities/urn:E3 + + + +05. GET urn:E1 to see V1 ======================== HTTP/1.1 200 OK Content-Length: 75 @@ -204,7 +243,7 @@ Link: see nothing +10. Query entities with q=V1==abc => see nothing ================================================ HTTP/1.1 200 OK Content-Length: 2 @@ -377,7 +490,7 @@ Date: REGEX(.*) [] -09. Query entities with q=V1==abc&expandValues=V1,V2 => see urn:E1 +11. Query entities with q=V1==abc&expandValues=V1,V2 => see urn:E1 ================================================================== HTTP/1.1 200 OK Content-Length: 77 @@ -397,7 +510,7 @@ Link: see urn:E2 +12. Query entities with q=V2==id&expandValues=V1,V2 => see urn:E2 ================================================================= HTTP/1.1 200 OK Content-Length: 86