BSONObj getErrorLabels(const OperationSessionInfoFromClient& sessionOptions, const std::string& commandName, ErrorCodes::Error code, bool hasWriteConcernError) { BSONArrayBuilder labelArray; // Note that we only apply the TransientTxnError label if the "autocommit" field is present in // the session options. When present, "autocommit" will always be false, so we don't check its // value. if (sessionOptions.getAutocommit() && isTransientTransactionError(code, hasWriteConcernError, commandName == "commitTransaction" || commandName == "coordinateCommitTransaction")) { // An error code for which isTransientTransactionError() is true indicates a transaction // failure with no persistent side effects. labelArray << txn::TransientTxnErrorFieldName; } if (ErrorCodes::isNonResumableChangeStreamError(code)) { labelArray << "NonResumableChangeStreamError"; } return (labelArray.arrSize() > 0) ? BSON("errorLabels" << labelArray.arr()) : BSONObj(); }
/* **************************************************************************** * * addTriggeredSubscriptions - */ static bool addTriggeredSubscriptions ( ContextRegistration cr, map<string, TriggeredSubscription*>& subs, std::string& err, std::string tenant ) { BSONArrayBuilder entitiesNoPatternA; std::vector<std::string> idJsV; std::vector<std::string> typeJsV; for (unsigned int ix = 0; ix < cr.entityIdVector.size(); ++ix) { // FIXME: take into account subscriptions with no type EntityId* enP = cr.entityIdVector[ix]; // The registration of isPattern=true entities is not supported, so we don't include them here if (enP->isPattern == "false") { entitiesNoPatternA.append(BSON(CASUB_ENTITY_ID << enP->id << CASUB_ENTITY_TYPE << enP->type << CASUB_ENTITY_ISPATTERN << "false")); idJsV.push_back(enP->id); typeJsV.push_back(enP->type); } } BSONArrayBuilder attrA; for (unsigned int ix = 0; ix < cr.contextRegistrationAttributeVector.size(); ++ix) { ContextRegistrationAttribute* craP = cr.contextRegistrationAttributeVector[ix]; attrA.append(craP->name); } BSONObjBuilder queryNoPattern; queryNoPattern.append(CASUB_ENTITIES, BSON("$in" << entitiesNoPatternA.arr())); if (attrA.arrSize() > 0) { // If we don't do this checking, the {$in: [] } in the attribute name part will // make the query fail // // queryB.append(CASUB_ATTRS, BSON("$in" << attrA.arr())); queryNoPattern.append("$or", BSON_ARRAY( BSON(CASUB_ATTRS << BSON("$in" << attrA.arr())) << BSON(CASUB_ATTRS << BSON("$size" << 0)))); } else { queryNoPattern.append(CASUB_ATTRS, BSON("$size" << 0)); } queryNoPattern.append(CASUB_EXPIRATION, BSON("$gt" << (long long) getCurrentTime())); // // This is JavaScript code that runs in MongoDB engine. As far as I know, this is the only // way to do a "reverse regex" query in MongoDB (see // http://stackoverflow.com/questions/15966991/mongodb-reverse-regex/15989520). // Note that although we are using a isPattern=true in the MongoDB query besides $where, we // also need to check that in the if statement in the JavaScript function given that a given // sub document could include both isPattern=true and isPattern=false documents // std::string idJsString = "[ "; for (unsigned int ix = 0; ix < idJsV.size(); ++ix) { if (ix != idJsV.size() - 1) { idJsString += "\""+idJsV[ix]+ "\" ,"; } else { idJsString += "\"" +idJsV[ix]+ "\""; } } idJsString += " ]"; std::string typeJsString = "[ "; for (unsigned int ix = 0; ix < typeJsV.size(); ++ix) { if (ix != typeJsV.size() - 1) { typeJsString += "\"" +typeJsV[ix] + "\" ,"; } else { typeJsString += "\"" + typeJsV[ix] + "\""; } } typeJsString += " ]"; std::string function = std::string("function()") + "{" + "enId = " + idJsString + ";" + "enType = " + typeJsString + ";" + "for (var i=0; i < this." + CASUB_ENTITIES + ".length; i++) {" + "if (this." + CASUB_ENTITIES + "[i]." + CASUB_ENTITY_ISPATTERN + " == \"true\") {" + "for (var j = 0; j < enId.length; j++) {" + "if (enId[j].match(this." + CASUB_ENTITIES + "[i]." + CASUB_ENTITY_ID + ") && this." + CASUB_ENTITIES + "[i]." + CASUB_ENTITY_TYPE + " == enType[j]) {" + "return true; " + "}" + "}" + "}" + "}" + "return false; " + "}"; LM_T(LmtMongo, ("JS function: %s", function.c_str())); std::string entPatternQ = CSUB_ENTITIES "." CSUB_ENTITY_ISPATTERN; BSONObjBuilder queryPattern; queryPattern.append(entPatternQ, "true"); queryPattern.append(CASUB_EXPIRATION, BSON("$gt" << (long long) getCurrentTime())); queryPattern.appendCode("$where", function); auto_ptr<DBClientCursor> cursor; BSONObj query = BSON("$or" << BSON_ARRAY(queryNoPattern.obj() << queryPattern.obj())); TIME_STAT_MONGO_READ_WAIT_START(); DBClientBase* connection = getMongoConnection(); if (!collectionQuery(connection, getSubscribeContextAvailabilityCollectionName(tenant), query, &cursor, &err)) { TIME_STAT_MONGO_READ_WAIT_STOP(); releaseMongoConnection(connection); return false; } TIME_STAT_MONGO_READ_WAIT_STOP(); /* For each one of the subscriptions found, add it to the map (if not already there) */ while (moreSafe(cursor)) { BSONObj sub; std::string err; if (!nextSafeOrErrorF(cursor, &sub, &err)) { LM_E(("Runtime Error (exception in nextSafe(): %s - query: %s)", err.c_str(), query.toString().c_str())); continue; } BSONElement idField = getFieldF(sub, "_id"); // // BSONElement::eoo returns true if 'not found', i.e. the field "_id" doesn't exist in 'sub' // // Now, if 'getFieldF(sub, "_id")' is not found, if we continue, calling OID() on it, then we get // an exception and the broker crashes. // if (idField.eoo() == true) { std::string details = std::string("error retrieving _id field in doc: '") + sub.toString() + "'"; alarmMgr.dbError(details); continue; } alarmMgr.dbErrorReset(); std::string subIdStr = idField.OID().toString(); if (subs.count(subIdStr) == 0) { LM_T(LmtMongo, ("adding subscription: '%s'", sub.toString().c_str())); // // FIXME P4: Once ctx availability notification formats get defined for NGSIv2, // the first parameter for TriggeredSubscription will have "normalized" as default value // TriggeredSubscription* trigs = new TriggeredSubscription( sub.hasField(CASUB_FORMAT)? stringToRenderFormat(getStringFieldF(sub, CASUB_FORMAT)) : NGSI_V1_LEGACY, getStringFieldF(sub, CASUB_REFERENCE), subToAttributeList(sub)); subs.insert(std::pair<string, TriggeredSubscription*>(subIdStr, trigs)); } } releaseMongoConnection(connection); return true; }
/* **************************************************************************** * * associationsQuery - */ static bool associationsQuery ( EntityIdVector* enV, AttributeList* attrL, const std::string& scope, MetadataVector* mdV, std::string* err, const std::string& tenant, int offset, int limit, bool details, const std::vector<std::string>& servicePathV ) { DBClientBase* connection = getMongoConnection(); /* Note that SCOPE_VALUE_ASSOC_SOURCE means that the argument is a target (so we use ASSOC_TARGET_ENT and * ASSOC_ATTRS_TARGET in the query), while SCOPE_VALUE_ASSOC_TARGET means that the argument is a source (so we * use ASSOC_SOURCE_ENT and ASSOC_ATTRS_source in the query) */ BSONObjBuilder queryB; /* Build query (entity part) */ BSONArrayBuilder enArray; for (unsigned int ix = 0; ix < enV->size(); ++ix) { enArray.append(BSON(ASSOC_ENT_ID << enV->get(ix)->id << ASSOC_ENT_TYPE << enV->get(ix)->type)); } BSONObj queryEn; if (scope == SCOPE_VALUE_ASSOC_SOURCE) { queryB.append(ASSOC_TARGET_ENT, BSON("$in" << enArray.arr())); } else { // SCOPE_VALUE_ASSOC_TARGET queryB.append(ASSOC_SOURCE_ENT, BSON("$in" << enArray.arr())); } /* Build query (attribute part) */ BSONArrayBuilder attrArray; for (unsigned int ix = 0; ix < attrL->size() ; ++ix) { attrArray.append(attrL->get(ix)); } std::string attrField; if (scope == SCOPE_VALUE_ASSOC_SOURCE) { attrField = ASSOC_ATTRS "." ASSOC_ATTRS_TARGET; } else { // SCOPE_VALUE_ASSOC_TARGET attrField = ASSOC_ATTRS "." ASSOC_ATTRS_SOURCE; } // If there are no attributes specified we want them all if (attrArray.arrSize() != 0) { queryB.append(attrField, BSON("$in" << attrArray.arr())); } /* Do the query in MongoDB */ auto_ptr<DBClientCursor> cursor; Query query(queryB.obj()); Query sortCriteria = query.sort(BSON("_id" << 1)); try { LM_T(LmtMongo, ("query() in '%s' collection: '%s'", getAssociationsCollectionName(tenant).c_str(), query.toString().c_str())); mongoSemTake(__FUNCTION__, "query in AssociationsCollection"); cursor = connection->query(getAssociationsCollectionName(tenant).c_str(), query, limit, offset); /* * We have observed that in some cases of DB errors (e.g. the database daemon is down) instead of * raising an exception, the query() method sets the cursor to NULL. In this case, we raise the * exception ourselves */ if (cursor.get() == NULL) { throw DBException("Null cursor from mongo (details on this is found in the source code)", 0); } mongoSemGive(__FUNCTION__, "query in AssociationsCollection"); LM_I(("Database Operation Successful (%s)", query.toString().c_str())); } catch (const DBException &e) { mongoSemGive(__FUNCTION__, "query in AssociationsCollection (DBException)"); *err = std::string("collection: ") + getAssociationsCollectionName(tenant).c_str() + " - query(): " + query.toString() + " - exception: " + e.what(); LM_E(("Database Error ('%s', '%s')", query.toString().c_str(), err->c_str())); return false; } catch (...) { mongoSemGive(__FUNCTION__, "query in AssociationsCollection (Generic Exception)"); *err = std::string("collection: ") + getAssociationsCollectionName(tenant).c_str() + " - query(): " + query.toString() + " - exception: " + "generic"; LM_E(("Database Error ('%s', '%s')", query.toString().c_str(), err->c_str())); return false; } /* Process query result */ while (cursor->more()) { BSONObj r = cursor->next(); LM_T(LmtMongo, ("retrieved document: '%s'", r.toString().c_str())); std::string name = STR_FIELD(r, "_id"); std::string srcEnId = STR_FIELD(r.getField(ASSOC_SOURCE_ENT).embeddedObject(), ASSOC_ENT_ID); std::string srcEnType = STR_FIELD(r.getField(ASSOC_SOURCE_ENT).embeddedObject(), ASSOC_ENT_TYPE); std::string tgtEnId = STR_FIELD(r.getField(ASSOC_TARGET_ENT).embeddedObject(), ASSOC_ENT_ID); std::string tgtEnType = STR_FIELD(r.getField(ASSOC_TARGET_ENT).embeddedObject(), ASSOC_ENT_TYPE); Metadata* md = new Metadata(name, "Association"); md->association.entityAssociation.source.fill(srcEnId, srcEnType, "false"); md->association.entityAssociation.target.fill(tgtEnId, tgtEnType, "false"); std::vector<BSONElement> attrs = r.getField(ASSOC_ATTRS).Array(); for (unsigned int ix = 0; ix < attrs.size(); ++ix) { std::string srcAttr = STR_FIELD(attrs[ix].embeddedObject(), ASSOC_ATTRS_SOURCE); std::string tgtAttr = STR_FIELD(attrs[ix].embeddedObject(), ASSOC_ATTRS_TARGET); AttributeAssociation* attrAssoc = new AttributeAssociation(); attrAssoc->source = srcAttr; attrAssoc->target = tgtAttr; md->association.attributeAssociationList.push_back(attrAssoc); } mdV->push_back(md); } return true; }
/* **************************************************************************** * * addTriggeredSubscriptions * */ static bool addTriggeredSubscriptions(ContextRegistration cr, map<string, TriggeredSubscription*>& subs, std::string& err, std::string tenant) { DBClientBase* connection = NULL; BSONArrayBuilder entitiesNoPatternA; std::vector<std::string> idJsV; std::vector<std::string> typeJsV; for (unsigned int ix = 0; ix < cr.entityIdVector.size(); ++ix ) { //FIXME: take into account subscriptions with no type EntityId* enP = cr.entityIdVector.get(ix); /* The registration of isPattern=true entities is not supported, so we don't include them here */ if (enP->isPattern == "false") { entitiesNoPatternA.append(BSON(CASUB_ENTITY_ID << enP->id << CASUB_ENTITY_TYPE << enP->type << CASUB_ENTITY_ISPATTERN << "false")); idJsV.push_back(enP->id); typeJsV.push_back(enP->type); } } BSONArrayBuilder attrA; for (unsigned int ix = 0; ix < cr.contextRegistrationAttributeVector.size(); ++ix) { ContextRegistrationAttribute* craP = cr.contextRegistrationAttributeVector.get(ix); attrA.append(craP->name); } BSONObjBuilder queryNoPattern; queryNoPattern.append(CASUB_ENTITIES, BSON("$in" << entitiesNoPatternA.arr())); if (attrA.arrSize() > 0) { /* If we don't do this checking, the {$in: [] } in the attribute name part will * make the query fail*/ //queryB.append(CASUB_ATTRS, BSON("$in" << attrA.arr())); queryNoPattern.append("$or", BSON_ARRAY( BSON(CASUB_ATTRS << BSON("$in" << attrA.arr())) << BSON(CASUB_ATTRS << BSON("$size" << 0)) )); } else { queryNoPattern.append(CASUB_ATTRS, BSON("$size" << 0)); } queryNoPattern.append(CASUB_EXPIRATION, BSON("$gt" << (long long) getCurrentTime())); /* This is JavaScript code that runs in MongoDB engine. As far as I know, this is the only * way to do a "reverse regex" query in MongoDB (see * http://stackoverflow.com/questions/15966991/mongodb-reverse-regex/15989520). * Note that although we are using a isPattern=true in the MongoDB query besides $where, we * also need to check that in the if statement in the JavaScript function given that a given * sub document could include both isPattern=true and isPattern=false documents */ std::string idJsString = "[ "; for (unsigned int ix = 0; ix < idJsV.size(); ++ix ) { if (ix != idJsV.size()-1) { idJsString += "\""+idJsV[ix]+ "\" ,"; } else { idJsString += "\"" +idJsV[ix]+ "\""; } } idJsString += " ]"; std::string typeJsString = "[ "; for (unsigned int ix = 0; ix < typeJsV.size(); ++ix ) { if (ix != typeJsV.size()-1) { typeJsString += "\"" +typeJsV[ix] + "\" ,"; } else { typeJsString += "\"" + typeJsV[ix] + "\""; } } typeJsString += " ]"; std::string function = std::string("function()") + "{" + "enId = "+idJsString+ ";" + "enType = "+typeJsString+ ";" + "for (var i=0; i < this."+CASUB_ENTITIES+".length; i++) {" + "if (this."+CASUB_ENTITIES+"[i]."+CASUB_ENTITY_ISPATTERN+" == \"true\") {" + "for (var j=0; j < enId.length; j++) {" + "if (enId[j].match(this."+CASUB_ENTITIES+"[i]."+CASUB_ENTITY_ID+") && this."+CASUB_ENTITIES+"[i]."+CASUB_ENTITY_TYPE+" == enType[j]) {" + "return true; " + "}" + "}" + "}" + "}" + "return false; " + "}"; LM_T(LmtMongo, ("JS function: %s", function.c_str())); std::string entPatternQ = CSUB_ENTITIES "." CSUB_ENTITY_ISPATTERN; BSONObjBuilder queryPattern; queryPattern.append(entPatternQ, "true"); queryPattern.append(CASUB_EXPIRATION, BSON("$gt" << (long long) getCurrentTime())); queryPattern.appendCode("$where", function); BSONObj query = BSON("$or" << BSON_ARRAY(queryNoPattern.obj() << queryPattern.obj())); /* Do the query */ auto_ptr<DBClientCursor> cursor; LM_T(LmtMongo, ("query() in '%s' collection: '%s'", getSubscribeContextAvailabilityCollectionName(tenant).c_str(), query.toString().c_str())); try { connection = getMongoConnection(); cursor = connection->query(getSubscribeContextAvailabilityCollectionName(tenant).c_str(), query); /* * We have observed that in some cases of DB errors (e.g. the database daemon is down) instead of * raising an exception, the query() method sets the cursor to NULL. In this case, we raise the * exception ourselves */ if (cursor.get() == NULL) { throw DBException("Null cursor from mongo (details on this is found in the source code)", 0); } releaseMongoConnection(connection); LM_I(("Database Operation Successful (%s)", query.toString().c_str())); } catch (const DBException &e) { releaseMongoConnection(connection); err = std::string("collection: ") + getSubscribeContextAvailabilityCollectionName(tenant).c_str() + " - query(): " + query.toString() + " - exception: " + e.what(); LM_E(("Database Error (%s)", err.c_str())); return false; } catch (...) { releaseMongoConnection(connection); err = std::string("collection: ") + getSubscribeContextAvailabilityCollectionName(tenant).c_str() + " - query(): " + query.toString() + " - exception: " + "generic"; LM_E(("Database Error (%s)", err.c_str())); return false; } /* For each one of the subscriptions found, add it to the map (if not already there) */ while (cursor->more()) { BSONObj sub = cursor->next(); BSONElement idField = sub.getField("_id"); // // BSONElement::eoo returns true if 'not found', i.e. the field "_id" doesn't exist in 'sub' // // Now, if 'sub.getField("_id")' is not found, if we continue, calling OID() on it, then we get // an exception and the broker crashes. // if (idField.eoo() == true) { LM_E(("Database Error (error retrieving _id field in doc: %s)", sub.toString().c_str())); continue; } std::string subIdStr = idField.OID().toString(); if (subs.count(subIdStr) == 0) { LM_T(LmtMongo, ("adding subscription: '%s'", sub.toString().c_str())); TriggeredSubscription* trigs = new TriggeredSubscription(sub.hasField(CASUB_FORMAT) ? stringToFormat(STR_FIELD(sub, CASUB_FORMAT)) : XML, STR_FIELD(sub, CASUB_REFERENCE), subToAttributeList(sub)); subs.insert(std::pair<string, TriggeredSubscription*>(subIdStr, trigs)); } } return true; }