moleculerjs / moleculer-db

:battery: Database access service mixins for Moleculer
https://moleculer.services/
MIT License
152 stars 122 forks source link

MongoDB "populate" does not work when idField != "_id" #254

Closed theZappr closed 3 years ago

theZappr commented 3 years ago

Hello all,

Populates returns {undefined:{entity}} because MongoDB Adapter does not transform "_id" to "id" before building the populates response.

When _get finds an array of objects it transforms the array and then params.mapping kicks in and the entity gets put into {[id]:entity} problem is the code in _get gets the id from the origDoc (which has _id) not the transformed "json" variable which has the desired "id" field.

This yields an id of undefined. so the resulting array gets squashed into {undefined:{theLastEntityFound}} rather than {[id0]:...[id1]:...}

Some debugging found that moleculer-db/src/index.js _get the "origDoc" has the untransformed " _id" when using the mongo adapter but the transformed "id" when using the db adapter.

My POC fix is below but this just gets it working. The issue is somewhere in the mongo db adapter. Maybe something is missing in one of the overridden methods, i.e. it is not transforming the ids on "populate" responses?

.then(json => {
  if (_.isArray(json) && params.mapping === true) {
    let res = {};
    json.forEach((doc, i) => {
      // origDoc is pre-transform so has _id not id
      // use doc instead 
      // const id = origDoc[i][this.settings.idField]; <<< busted
      const id = doc[this.settings.idField]; /// <<< "demo fix" problem is really elsewhere
      res[id] = doc;
    });
    return res;
  } else if (_.isObject(json) && params.mapping === true) {
    let res = {};
    // LIKELY this is brokedid as well, not tried
    const id = origDoc[this.settings.idField];
    res[id] = json;
    return res;
     }
     return json;
});

Reproducing the issue

// package.json
  "dependencies": {
    "moleculer": "^0.14.12",
    "moleculer-db": "^0.8.12",
    "moleculer-db-adapter-mongo": "^0.4.11",
    "moleculer-repl": "^0.6.4"
  }
"use strict";

const { ServiceBroker } = require("moleculer");
const DbService = require("moleculer-db");
const MongoDBAdapter = require("moleculer-db-adapter-mongo");

const broker = new ServiceBroker({
    validation:true,
    logger: {
      type: 'Console',
      options: {
        level: 'debug',
      }
    },

});

broker.createService({
  name:"service1",
  mixins:[DbService],
  // add the mongo adapter and you will see the issue
  // adapter: new MongoDBAdapter("mongodb://...",{useNewURrlParser:true},"adb"),
  collection:"service1",
  settings:{
    idField:"id",
    fields:["id","name","service2Items"],
    entityValidator:{
      name:"string",
      service2Items:"array"
    },
    populates:{
      "service2Items":"service2.get"
    }
  }
});

broker.createService({
  name:"service2",
  mixins:[DbService],
  // add the mongo adapter and you will see the issue
  // adapter: new MongoDBAdapter("mongodb://...",{useNewURrlParser:true},"adb"),
  collection:"service2",
  settings:{
    idField:"id",
    fields:["id","name"],
    entityValidator:{
      name:"string"
    }
  }
});

let idA,idB,idX

broker.start()
  .then(() => broker.call("service2.create", {name:"A"}))
  .then(res => {idA = res.id;console.log(res)})
  .then(() => broker.call("service2.create", {name:"B"}))
  .then(res => {idB = res.id;console.log(res)})
  .then(() => broker.call("service1.create", {name:"X", 
    service2Items:[idA,idB] }))
  .then(res => {idX = res.id; console.log(res)})
  .then(() => broker.call("service1.get", { id:idX, 
    populate:"service2Items" }))
  .then(output => {console.log({output})})

MemoryAdapter output - Correct!

// consoled origDoc note has "id" field
// nodemodules/moleculer-db/src/index.js:854
// console.log({origDoc, idField:this.settings.idField})
origDoc: [
    { name: 'A', id: 'E3M6IEFWujVnjG2l' },
    { name: 'B', id: '3pz8crkvuOAEnt2i' }
  ]

// final output : see we have items :D
{
  output: {
    id: 'sVnPIgg7S070hzO5',
    name: 'X',
    service2Items: [ [Object], [Object] ]
  }
}

MongoDB output - Not so much!

// consoled origDoc note has "_id" field :-O
// nodemodules/moleculer-db/src/index.js:854
// console.log({origDoc, idField:this.settings.idField})
origDoc: [
    { _id: 603fbeb9ce1d1563e5f4ec3d, name: 'A' },
    { _id: 603fbeb9ce1d1563e5f4ec3e, name: 'B' }
  ]

// final output : see no items :(
{
  output: { id: '603fbeb9ce1d1563e5f4ec3f', name: 'X', service2Items: [] }
}
icebob commented 3 years ago

Thanks, the repro example was very helpful, easy to find the bug. It's fixed, now it returns the correct populated docs: image