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

似乎有序选项设置为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()callback在源代码行703上返回。

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

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

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

就目前来看,似乎没有办法同时得到错误和结果。

我对吗?

UPDATE

我已经运行了一个基于Neil Lunn的答案的testing代码。 由于我的Node.js(4.4.5)不理解asynchronous/等待,我必须用明确的Promise重写testing代码。

testing代码如下:

 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 } } ] 

现在,我再次运行代码,testDatavariables修改如下:

 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等)如上所述,这是驱动程序源代码行669上的一个“错误”。

在两次testing运行中,result参数的types都不是Collection~insertWriteOpResult 。 第一个是Collection~bulkWriteOpCallback ,第二个是更多的内部。 所以,在这种情况下,API文档是错误的。 这是由于我上面告诉过的535和669行上的“bug”造成的。

所以,即使结果可以被使用(事实上,结果hasWriteErrors()getWriteErrors()如Neil Lunn所说),因为这种行为没有logging,我怀疑它可以在更高版本上进行更改,恕不另行通知。代码将会中断。

这个问题实际上只是以“承诺”如何解决以及错误信息是如何传递的,但是当然真正的核心问题是,当任何“批量”操作时,实际上返回“错误”和结果信息设置为{ ordered: false } 。 正如NODE-1158所述的驱动程序所解释的那样,在3.x版本的驱动程序中,它还包含指向未来分支提交问题的提交的链接。

为此的“解决方法”是要知道,“这两个”的结果是一个错误信息出现在BulkWriteResult对象中,作为结果在任何这样的方法的“callback”调用返回(注意insertMany()和甚至bulkWrite()实际包装一个基础的批量API实现 )。

用列表来演示:

 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返回,它应该由驱动程序的实现代码来表示,它只是简单地将它的callback结果传递给它的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 ,尽pipe我们设置了{ ordered: false }但在响应中没有“结果”信息。 我们可以在错误信息的详细检查上看到, WriteError列表确实具有每个产生的重复键错误的详细信息。

所以成功写入批处理中的所有内容都没有抛出错误,但没有Promise中获取任何内容。 但是这不是真正的底层方法,它仍然使用callback来实现。

第二次尝试将这个callback“手动”包装起来,所以我们实际上可以通过改变行为并在err出现时传递result对象来reject result 。 这告诉我们一个不同的故事:

 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 } } ] 

由于我们没有传回err我们现在看到catch块中的BulkWriteResult 。 我们知道我们到了那里,因为我们正在运行的特定代码来检查结果。

常规的结果确实有一些作为修改或插入的计数,以及insertedIds的列表。 我们还可以从检查中看出hasWriteErrors()返回true ,我们可以获得已经序列化的WriteError列表,以便更好地查看。

在3.x中修复

正如链接问题所述,实际的修复只会出现在支持MongoDB 3.6的3.x驱动程序版本中。 “修复”基本上是在“较低级别”上完成的,根本返回BulkWriteResult ,而是使err返回一个BulkWriteError

这实际上使得事情与其他一些驱动程序已经正确地实现这一点已经一致。 老实说,与传统的“节点式”callback有点“宿醉”,总是返回“both”错误和响应。

所以把它带入“只是一个错误”,使得事情更加一致,并且像你通常所期望的那样工作。

有一点需要注意的是, MongoDB Node.js本地驱动程序中的相关问题默默吞下了bulkWriteexception。 在JIRA问题中引用显示实现中的实际的bulkWrite()方法(它不是“直接” insertMany()包装的)有一个稍微不同的问题,因为代码期望的是“没有错误” resultnull并且如所描述的那样。

所以反过来说,在那里我们从来没有得到使用Promise的默认实现中的exception。 然而,完全相同的处理方法是应该应用的,通过手动包装callback,并通过reject发送result优先err当这两个返回非null

按照描述的方法解决这些问题,最好在新的驱动程序可用时立即转移到新的驱动程序中。 无论如何,这应该是相当快的。