函数insertMany()无序:获取错误和结果的正确方法?

时间:2017-10-20 01:31:54

标签: javascript node.js mongodb

似乎将ordero选项设置为false的MongoDB insertMany()函数可以比有序选项设置为true更有效地插入文档。即使多个文档无法插入,它也可以继续插入文档。

但是我发现没有干净的方法来获取每个失败的文档错误和整体命令结果。

(顺便说一句,我正在使用Node.js驱动程序API 2.2。我将从现在开始参考驱动程序的源代码:http://mongodb.github.io/node-mongodb-native/2.2/api/lib_collection.js.html

首先,如果使用Promise,则无法同时获取错误和结果。在源代码行540上,insertMany()返回错误或结果 - 而不是两者,而bulkWrite()回调在源代码行703上返回。

其次,如果使用回调,事情会变得更糟。当bulkWrite()同时使用错误和结果调用回调时,insertMany()会使用错误和结果调用回调,但结果是BulkWrite的结果,而不是正确转换的结果到InsertManyResults。请参阅源代码行535.我认为这是一个错误。

即使对于bulkWrite(),当错误数为1时,它也不会将结果正确转换为其格式。请参阅源代码行669.我认为这也是一个错误。

现在我认为Node.js驱动程序根本不准备处理这种情况。

目前,似乎无法正确获取错误和结果。

我是对的吗?

更新

我已经运行了一个基于Neil Lunn的答案的测试代码。既然我的 Node.js(4.4.5)不理解async / await,我不得不用显式Promises重写测试代码。

测试代码如下:

function doTest()
{
    var MongoClient = require('mongodb').MongoClient;
    var testData = [ 1,2,2,3,3,4,5,6,7,8,9 ];
    var db;

    return MongoClient.connect('mongodb://127.0.0.1/test')
    .then(function (_db)
    {
        db = _db;
        return db.createCollection('test');
    })
    .then(function ()
    {
        return db.collection('test').deleteMany({})
        .then(function ()
        {
            return db.collection('test').insertMany(
                testData.map(function (_id)
                {
                    return { _id: _id };
                }),
                { ordered: false })
            .then(function (result)
            {
                console.log('Promise: result', result);
            }, function (err)
            {
                console.log('Promise: error', err);
            });
        })
        .then(function ()
        {
            return db.collection('test').deleteMany({});
        })
        .then(function ()
        {
            return new Promise(function (resolve, reject)
            {
                return db.collection('test').insertMany(
                    testData.map(function (_id)
                    {
                        return { _id: _id };
                    }),
                    { ordered: false },
                    function (err, result)
                {
                    console.log('callback: error', err);
                    console.log('callback: result', result);
                    console.log('callback: result.hasWriteErrors', result.hasWriteErrors());
                    console.log('callback: result.getWriteErrors',
                        JSON.stringify(result.getWriteErrors(), null, 2));
                    resolve();
                });
            });
        });
    })
    .catch(function (err)
    {
        console.log('catch', err);
    })
    .then(function ()
    {
        db.close();
    });
}
doTest();

结果如下:

Promise: error { [MongoError: write operation failed]
  name: 'MongoError',
  message: 'write operation failed',
  driver: true,
  code: 11000,
  writeErrors: 
   [ { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] },
     { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] } ] }
callback: error { [MongoError: write operation failed]
  name: 'MongoError',
  message: 'write operation failed',
  driver: true,
  code: 11000,
  writeErrors: 
   [ { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] },
     { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] } ] }
callback: result { ok: [Getter],
  nInserted: [Getter],
  nUpserted: [Getter],
  nMatched: [Getter],
  nModified: [Getter],
  nRemoved: [Getter],
  getInsertedIds: [Function],
  getUpsertedIds: [Function],
  getUpsertedIdAt: [Function],
  getRawResponse: [Function],
  hasWriteErrors: [Function],
  getWriteErrorCount: [Function],
  getWriteErrorAt: [Function],
  getWriteErrors: [Function],
  getLastOp: [Function],
  getWriteConcernError: [Function],
  toJSON: [Function],
  toString: [Function],
  isOk: [Function],
  insertedCount: 9,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds: 
   { '0': 1,
     '1': 2,
     '2': 2,
     '3': 3,
     '4': 3,
     '5': 4,
     '6': 5,
     '7': 6,
     '8': 7,
     '9': 8,
     '10': 9 },
  n: 9 }
callback: result.hasWriteErrors true
callback: result.getWriteErrors [
  {
    "code": 11000,
    "index": 2,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 2 }",
    "op": {
      "_id": 2
    }
  },
  {
    "code": 11000,
    "index": 4,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
    "op": {
      "_id": 3
    }
  }
]

现在,我再次运行代码,testData变量修改如下:

var testData = [ 1,2,3,3,4,5,6,7,8,9 ];

在这种情况下,错误的数量将是1,而不是2,因为重复的' 2'被删除了。

结果如下:

Promise: error { [MongoError: E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }]
  name: 'MongoError',
  message: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  driver: true,
  code: 11000,
  index: 3,
  errmsg: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  getOperation: [Function],
  toJSON: [Function],
  toString: [Function] }
callback: error { [MongoError: E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }]
  name: 'MongoError',
  message: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  driver: true,
  code: 11000,
  index: 3,
  errmsg: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  getOperation: [Function],
  toJSON: [Function],
  toString: [Function] }
callback: result { ok: [Getter],
  nInserted: [Getter],
  nUpserted: [Getter],
  nMatched: [Getter],
  nModified: [Getter],
  nRemoved: [Getter],
  getInsertedIds: [Function],
  getUpsertedIds: [Function],
  getUpsertedIdAt: [Function],
  getRawResponse: [Function],
  hasWriteErrors: [Function],
  getWriteErrorCount: [Function],
  getWriteErrorAt: [Function],
  getWriteErrors: [Function],
  getLastOp: [Function],
  getWriteConcernError: [Function],
  toJSON: [Function],
  toString: [Function],
  isOk: [Function] }
callback: result.hasWriteErrors true
callback: result.getWriteErrors [
  {
    "code": 11000,
    "index": 3,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
    "op": {
      "_id": 3
    }
  }
]

错误和结果的格式与第一次运行完全不同。

  • 错误没有writeErrors字段。
  • 结果没有'转换'领域。 (insertedCount,matchedCount等)这是一个' bug'在我上面已经讲过的驱动程序源代码行669上。

在两次测试运行中,结果参数的类型都不是Collection~insertWriteOpResult。第一个是Collection~bulkWriteOpCallback,第二个是内部的。{1}。因此,在这种情况下,API文档是错误的。这是由“错误”引起的。正如我上面所说的那样,在第535和669行。

所以,即使结果可以使用(实际上,结果有hasWriteErrors()getWriteErrors(),Neil Lunn告诉),由于这种行为没有记录,我怀疑它可以改变更新版本,恕不另行通知,我的代码将会中断。

1 个答案:

答案 0 :(得分:1)

这个问题实际上只与#34;承诺"解决了错误信息的传递方式,但当然真正的核心问题是 实际上是"""任何"批量"时的错误和结果信息操作设置为{ ordered: false }。正如NODE-1158中所述,3.x版本的驱动程序正在解决这个问题,该驱动程序还包含指向未来分支中修复问题的提交的链接。

"解决方法"因为这是要意识到"两者"结果在BulkWriteResult对象中出现错误信息,作为"回调"中的结果返回调用任何此类方法(注意insertMany()甚至bulkWrite()实际上包装了基础Bulk API Implementation)。

使用列表进行演示:

const MongoClient = require('mongodb').MongoClient;

const uri = 'mongodb://localhost/test';
const testData = [1,2,3,3,4,5,6,6,7,8,9];

(async function() {

  let db;

  try {

    db = await MongoClient.connect(uri);

    await db.collection('test').remove();

    // Expect an error here - but it's just the errors
    try {
      let result = await db.collection('test').insertMany(
        testData.map( _id => ({ _id }) ),
        { "ordered": false }
      );
      console.log(result);   // never gets here
    } catch(e) {
      console.dir(e);
      console.log(JSON.stringify(e.writeErrors,undefined,2));
    }

    await db.collection('test').remove();
    // Wrapped callback so we see what happens

    try {
      let result = await new Promise((resolve,reject) => 
        db.collection('test').insertMany(
          testData.map( _id => ({ _id }) ),
          { "ordered": false },
          (err,result) => {
            if (err) reject(result);    // Because the errors are here as well
            resolve(result);
          }
        )
      );
      console.log(result);  // Never gets here
    } catch(e) {
      console.dir(e);
      console.log(e.hasWriteErrors());
      console.log(JSON.stringify(e.getWriteErrors(),undefined,2));
    }

  } catch(e) {
    console.error(e);
  } finally {
    db.close();
  }

})();

因此,有两个代码块试图将insertMany()与一系列值一起使用,这些值会为某些值产生重复键错误。

在第一次尝试中,我们使用默认的Promise返回值,应该由驱动程序的实现代码指示,它只是将err回调结果传递给它包含在其中的方法& #39; s reject()声明。这意味着我们转到此处的catch块,并将错误信息作为输出生成:

{ MongoError: [object Object]
    at Function.MongoError.create (/home/neillunn/projects/bulkerror/node_modules/mongodb-core/lib/error.js:31:11)
    at toError (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:139:22)
    at /home/neillunn/projects/bulkerror/node_modules/mongodb/lib/collection.js:701:23
    at handleCallback (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:120:56)
    at /home/neillunn/projects/bulkerror/node_modules/mongodb/lib/bulk/unordered.js:465:9
    at handleCallback (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:120:56)
    at resultHandler (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/bulk/unordered.js:413:5)
    at /home/neillunn/projects/bulkerror/node_modules/mongodb-core/lib/connection/pool.js:469:18
    at _combinedTickCallback (internal/process/next_tick.js:131:7)
    at process._tickCallback (internal/process/next_tick.js:180:9)
  name: 'MongoError',
  message: 'write operation failed',
  driver: true,
  code: 11000,
  writeErrors:
   [ WriteError {
       code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] },
     WriteError {
       code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] } ] }
[
  {
    "code": 11000,
    "index": 3,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
    "op": {
      "_id": 3
    }
  },
  {
    "code": 11000,
    "index": 7,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 6 }",
    "op": {
      "_id": 6
    }
  }
]

请注意,这是一个包裹的MongoError,尽管我们设置了{ ordered: false },但没有"结果"响应中的信息。我们可以在详细检查错误信息时看到WriteError列表确实有关于每个重复键错误的详细信息。

因此,批处理中没有出现错误的所有内容都成功写入,但是在Promise 中可以获得的任何中都没有报告。但是底层方法并不是这样,它仍然使用回调实现。

第二次尝试包含这个回调"手动",所以我们实际上可以通过改变行为来看到结果,并在{{1}时将result对象传递给reject }} 存在。这告诉我们一个不同的故事:

err

由于我们没有传回BulkWriteResult { ok: [Getter], nInserted: [Getter], nUpserted: [Getter], nMatched: [Getter], nModified: [Getter], nRemoved: [Getter], getInsertedIds: [Function], getUpsertedIds: [Function], getUpsertedIdAt: [Function], getRawResponse: [Function], hasWriteErrors: [Function], getWriteErrorCount: [Function], getWriteErrorAt: [Function], getWriteErrors: [Function], getLastOp: [Function], getWriteConcernError: [Function], toJSON: [Function], toString: [Function], isOk: [Function], insertedCount: 9, matchedCount: 0, modifiedCount: 0, deletedCount: 0, upsertedCount: 0, upsertedIds: {}, insertedIds: { '0': 1, '1': 2, '2': 3, '3': 3, '4': 4, '5': 5, '6': 6, '7': 6, '8': 7, '9': 8, '10': 9 }, n: 9 } true [ { "code": 11000, "index": 3, "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }", "op": { "_id": 3 } }, { "code": 11000, "index": 7, "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 6 }", "op": { "_id": 6 } } ] ,我们现在在catch块中看到err。我们知道我们到达那里是因为我们在该块中运行的特定代码来检查结果。

常规结果确实有一些东西作为修改或插入的计数,以及BulkWriteResult的列表。我们还可以从检查中看到insertedIds返回hasWriteErrors(),我们可以获取我们为了更好地查看而序列化的true列表。

已在3.x

中修复

如链接问题所述,实际修复只会出现在支持MongoDB 3.6的3.x驱动程序版本中。 "修复"基本上是在较低的水平" 根本不会返回WriteError,而是让BulkWriteResult返回err

这实际上使得事情与其他驱动程序已经正确实现这一点的方式更加一致。说实话,它有点像“宿醉”。来自传统的节点风格"回调,其中"两者"始终返回错误和响应。

所以把它带入"只是一个错误"使事情更加一致,并按照您的预期工作。

作为一点附注,JIRA问题中引用的MongoDB Node.js native driver silently swallows bulkWrite exception.中的相关问题显示了实施中的实际BulkWriteError方法(不是"直接"什么bulkWrite()换行)有一个稍微不同的问题"没有错误"实际上会被抛出,因为代码期望insertMany()result并且如所描述的那样不是。

所以相反的情况是正确的,在那里我们永远不会使用null在默认实现中找到catch的异常。然而,完全相同的处理方法是应该应用的方法,通过手动包装回调并优先Promise通过result优先于reject发送,而不是err }}

如上所述解决它们,并且最好在新驱动程序可用时立即移动到新驱动程序。无论如何,这应该很快。