Example #1
0
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;
}