Simple $lookup "left join" of the ObjectId of two collections not working in Mongo DB

209 views Asked by At

I am using Mongo DB Atlas with node.js and oboe (oboe streams the results to html). I am new to this all, just learning all these technologies, so explaining in simpler terms will be appreaciated.

The goal is to do a $lookup between two collections, in this case, the collection of 'review' to 'place'. I've researched similar answers, but the either didn't work or were using strings, not ObjectIds.

It's quite simple, just connect the ObjectIds of both of the collections, but I am not able to pull the data out from the "left joined" collection of 'places' when using oboe (see oboe code at bottom, FWIW).

Here is a look at a document from both collections, then the code. What am I doing wrong? I have tried converting them to strings and joining with .toString() and .str, plus put 'place_id.ObjectId' and '_id.ObjectId' for localField and foreignField. Another thing too, is how can I see what is in the cursor to know what I am getting? debug(cursor.ToArray()) didn't work. Thanks in advance.

review
({
  "_id": { "$oid": "5fd27fd9647f7bb815c4c946" },
  "place_id": { "$oid": "5fbc37c4fc13ae680b00002b" }, // connect this...
  "user_id": { "$oid": "5fbc10ecfc13ae232d000068" },
  "title": "Black Forest -- unforgettable!",
  "description": "The forest was great.",
  "score": { "$numberInt": "5" }
}


place
{
  "_id": { "$oid": "5fbc37c4fc13ae680b00002b" }, // connected to _id above
  "name": "Black Forest (Schwarzwald)",
  "category": "activity",
  "city": "Freiburg",
  "country": "Germany",
  "description": "The Black Forest (German: Schwarzwald [ˈʃvaʁtsvalt] (About this soundlisten)) is a large forested mountain range.]",
  "image": { "filename": "1607629020164_black_forest.jpg", "mime": "image/jpeg" },
  "state": ""
})

router.get('/', async (req, res, next) => {
  debug('get all reviews api');
  try {
    const q = req.query.q;
    const collation = { locale: 'en_US', strength: 1 };

    const matchStage = {};
    if (q) {
      matchStage.$text = { $search: q };
    }
    const pipeline = [
      {
        $match: matchStage,
      },
      {
        $lookup: {
          from: 'place',
          localField: 'place_id',
          foreignField: '_id',
          as: 'place',
        },
      },
    ];
    const connection = await db.connect();
    const cursor = connection.collection('review').aggregate(pipeline, { collation: collation });


    // write the JSON file
    res.type('application/json');
    res.write('[\n');
    for await (const doc of cursor) {
      res.write(JSON.stringify(doc));
      res.write(',\n');
    }
    res.end('null]');
  } catch (err) {
    sendError(err, res);
  }
});

The cursor goes to oboe and becomes an 'item'. I would expect to use a template string such as {item.place.name} to get the data when putting this into html. That's how I would access it, right?

    const performSearch = () => {
      seen = 0;
      $('stream-data-spinner').removeClass('d-none');
      $('#search-results').html('');
      const formData = $('#search-place-form').serialize();

      oboe('/api/review?' + formData)
        .node('![*]', (item) => {
          if (item) {
            chunk.push(item);
            if (chunk.length >= 1000) {
              showChunk(chunk);
            }
          }
          return oboe.drop;
        })
        .done((_) => {
          // show the last chunk
          showChunk(chunk);
          // hide the spinner
          outputSpinner.classList.add('d-none');
        })
        .fail((res) => {
          // show the error
          outputSeen.textContent = `ERROR: network error`;
          outputSeen.classList.add('text-danger');
          outputSpinner.classList.add('text-danger');
        });
    };
1

There are 1 answers

3
ray On BEST ANSWER

From your MongoDB aggregation query, your place field is an array. You may want to $unwind it to flatten it into object for your oboe code to access it.