Hello; trying to use simple transaction case as described in the documentation; I'm getting an error
ClientSession cannot be serialized to BSON.
at ClientSession.toBSON (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/sessions.js:225:11)
at serializeInto (/eliot-local-git/node_modules/bson/lib/bson/parser/serializer.js:895:23)
at serializeObject (/eliot-local-git/node_modules/bson/lib/bson/parser/serializer.js:347:18)
at serializeInto (/eliot-local-git/node_modules/bson/lib/bson/parser/serializer.js:937:17)
at BSON.serialize (/eliot-local-git/node_modules/bson/lib/bson/bson.js:63:28)
at Query.toBin (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/connection/commands.js:144:25)
at serializeCommands (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/connection/pool.js:1044:43)
at Pool.write (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/connection/pool.js:1260:3)
at Cursor._find (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/cursor.js:326:22)
at nextFunction (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/cursor.js:673:10)
at Cursor.next (/eliot-local-git/node_modules/mongoose/node_modules/mongodb-core/lib/cursor.js:824:3)
at Cursor._next (/eliot-local-git/node_modules/mongoose/node_modules/mongodb/lib/cursor.js:211:36)
at nextObject (/eliot-local-git/node_modules/mongoose/node_modules/mongodb/lib/operations/cursor_ops.js:186:10)
at next (/eliot-local-git/node_modules/mongoose/node_modules/mongodb/lib/operations/cursor_ops.js:165:3)
at executeOperation (/eliot-local-git/node_modules/mongoose/node_modules/mongodb/lib/utils.js:420:24)
at Cursor.next (/eliot-local-git/node_modules/mongoose/node_modules/mongodb/lib/cursor.js:253:10)
My code look like :
const session = await model.startSession();
const oneCompany = await model.findOne({}, {
projection: '_id',
session,
});
const oneLanguage = await classes.Language.schema.getModel().findOneAndUpdate({
_id: 'aaaaa0000000000000000000',
}, {
'name.description': 'lelz',
}, {
new: true,
projection: '_id',
session,
});
await session.commitTransaction();
session.endSession();
Versions I use :
node.js v9.11.1
[email protected]
[email protected] (mongo native node driver)
[email protected] (mongo native node driver)
mongodb v4.0.0 (database)
Thanks
I haven't used sessions at all yet, so I'm really not sure if this should work or not. I was able to create a complete repro script that demonstrates the outcome based on some assumptions about @GregoryNEUT's code sample.
#!/usr/bin/env node --no-deprecation
'use strict';
const { ATLASSRV } = require('/Users/lineus/.env');
const mongoose = require('mongoose');
mongoose.connect(ATLASSRV.replace(/test/, 'gh-6663'));
const conn = mongoose.connection;
const Schema = mongoose.Schema;
const schemaA = new Schema({
name: String
});
const schemaB = new Schema({
name: String
});
const A = mongoose.model('A', schemaA);
const B = mongoose.model('B', schemaB);
const a = new A({ name: 'Andrew' });
const b = new B({ name: 'Billy' });
async function run() {
await conn.dropDatabase();
await a.save();
await b.save();
const session = await A.startSession();
const foundA = await A.findOne({}, { session });
await B.findOneAndUpdate({}, { name: foundA.name }, { session });
await session.commitTransaction();
session.endSession();
let newB = await B.findOne({});
console.log(`hello from ${newB.name}`);
return conn.close();
}
run().catch(console.error);
issues: ./6663.js
Error: ClientSession cannot be serialized to BSON.
at ClientSession.toBSON (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/sessions.js:225:11)
at serializeInto (/Users/lineus/dev/Help/mongoose5/node_modules/bson/lib/bson/parser/serializer.js:895:23)
at serializeObject (/Users/lineus/dev/Help/mongoose5/node_modules/bson/lib/bson/parser/serializer.js:347:18)
at serializeInto (/Users/lineus/dev/Help/mongoose5/node_modules/bson/lib/bson/parser/serializer.js:937:17)
at BSON.serialize (/Users/lineus/dev/Help/mongoose5/node_modules/bson/lib/bson/bson.js:63:28)
at Query.toBin (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/connection/commands.js:144:25)
at serializeCommands (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/connection/pool.js:1044:43)
at Pool.write (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/connection/pool.js:1260:3)
at Cursor._find (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/cursor.js:326:22)
at nextFunction (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/cursor.js:673:10)
at Cursor.next (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb-core/lib/cursor.js:824:3)
at Cursor._next (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb/lib/cursor.js:211:36)
at nextObject (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb/lib/operations/cursor_ops.js:186:10)
at next (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb/lib/operations/cursor_ops.js:165:3)
at executeOperation (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb/lib/utils.js:420:24)
at Cursor.next (/Users/lineus/dev/Help/mongoose5/node_modules/mongodb/lib/cursor.js:253:10)
^C
issues:
something about creating the session on the first model, and passing it to a query on the second model feels wrong
This is a common gotcha that we should add docs for, but your issue is that the 2nd arg to Model.findOne()
is always a projection. You need to do await A.findOne({}, null, { session });
@vkarpov15 LOL 馃 I can't believe I missed that.
I had to make a couple of adjustments to my example that were wrong beyond the missing projection. In case it might help someone in the future here is the working code:
#!/usr/bin/env node --no-deprecation
'use strict';
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017,localhost:27018,localhost:27019/gh-6663?replicaSet=rs');
const conn = mongoose.connection;
const Schema = mongoose.Schema;
const schemaA = new Schema({
name: String
});
const schemaB = new Schema({
name: String
});
const A = mongoose.model('A', schemaA);
const B = mongoose.model('B', schemaB);
const a = new A({ name: 'Andrew' });
const b = new B({ name: 'Billy' });
async function run() {
await conn.dropDatabase();
await a.save();
await b.save();
const session = await A.startSession();
await session.startTransaction();
const foundA = await A.findOne({}, {}, { session });
await B.findOneAndUpdate({}, { name: foundA.name }, { session });
await session.commitTransaction();
session.endSession();
let newB = await B.findOne({});
console.log(`hello from ${newB.name}`);
return conn.close();
}
run().catch(console.error);
issues: ./6663.js
hello from Andrew
issues:
It is a real bug in debug environment.
mongoose.set('debug', true);
@vylan that's a very good point. Opened up #6712 to track and will fix asap
const {MongoClient} = require('mongodb');
const uri = 'mongodb://localhost:27017,localhost:27018,localhost:27019/test-mongodb';
MongoClient.connect(uri, {useNewUrlParser: true, replicaSet: 'rs'}).then(async (client) => {
// console.log(client);
const db = client.db();
await db.dropDatabase();
await db.collection('Account').insertMany([
{name: 'A', balance: 5},
{name: 'B', balance: 10}
]);
await transfer('A', 'B', 4); // Success
try {
await transfer('A', 'B', 6);
} catch (error) {
console.log(error);
}
// The actual transfer logic
async function transfer(from, to, amount) {
const session = client.startSession();
session.startTransaction();
try {
const opts = {session, returnOriginal: false};
const B = await db.collection('Account').findOneAndUpdate({name: to}, {$inc: {balance: amount}}, opts).then(res => res.value);
const A = await db.collection('Account').findOneAndUpdate({name: from}, {$inc: {balance: -amount}}, opts).then(res => res.value);
if (A.balance < 0) {
// If A would have negative balance, fail and abort the transaction
// `session.abortTransaction()` will undo the above `findOneAndUpdate()`
throw new Error('Insufficient funds: ' + (A.balance + amount));
}
await session.commitTransaction();
session.endSession();
return {from: A, to: B};
} catch (error) {
// If an error occurred, abort the whole transaction and
// undo any changes that might have happened
await session.abortTransaction();
session.endSession();
throw error; // Rethrow so calling function sees error
}
}
}).catch(console.log);
@vkarpov15 your example worked
Most helpful comment
This is a common gotcha that we should add docs for, but your issue is that the 2nd arg to
Model.findOne()
is always a projection. You need to doawait A.findOne({}, null, { session });