query that is not part of the transaction inside of a transaction - mongodb

What can potentially go wrong if you attempt to execute a query that is not part of the transaction inside of a transaction?
const session = client.startSession();
await session.withTransaction(async () => {
const coll1 = client.db('mydb1').collection('foo');
const coll2 = client.db('mydb2').collection('bar');
// Important:: You must pass the session to the operations
await coll1.insertOne({ abc: 1 } ); // does not have the session object
await coll2.insertOne({ xyz: 999 }, { session });
}, transactionOptions);

Related

Can an outside query modify a document involved in an ongoing transaction?

const client = new MongoClient(uri);
await client.connect();
await client
.db('mydb1')
.collection('foo');
const session = client.startSession();
const transactionOptions = {
readPreference: 'primary',
readConcern: { level: 'local' },
writeConcern: { w: 'majority' }
};
try {
await session.withTransaction(async () => {
const coll1 = client.db('mydb1').collection('foo');
await coll1.updateOne({user_id: 12344, paid: false }, { $set: { paid: true } } { session });
// long running computation after this line.
// what if another query deletes the document inserted above
// before this transaction completes.
await calls_third_party_payment_vendor_api_to_process_payment();
}, transactionOptions);
} finally {
await session.endSession();
await client.close();
}
What if the update document inside the transaction is simultaneously updated from an outside query before the transaction is committed?
What you have described is a transaction/operation conflict and
the operation blocks behind the transaction commit and infinitely retries with backoff logic until MaxTimeMS is reached
I wrote a report on MongoDB transactions, containing some examples in NodeJS too. if you are interested on the subject, I recommend you the paragraphs WiredTiger Cache and What happens when we create a multi-document transaction?

use Effect not working to bring up my product, using axios use params,

This code is not working for me i am trying to pull data from my mongodb
const ProductScreen = ({ match }) => {
const [product, setProduct] = useState({});
const { id } = useParams();
useEffect(() => {
const fetchProduct = async () => {
const { data } = await axios.get(
`/api/product/${encodeURIComponent(id)}`
);
setProduct(data);
};
fetchProduct();
}, []);
};
pull data from server of mongo db
It is possible when the component first mounts, id is null and useParams() doesn't get it till the second render. So add an if statement in your useEffect to make sure the id is present. Also add id to the dependency array, so if the id changes, you will refetch the data for it. Otherwise, with an empty dependency array, the useEffect will only run on first mount.
const ProductScreen = ({ match }) => {
const [product, setProduct] = useState({});
const { id } = useParams();
useEffect(() => {
const fetchProduct = async () => {
const { data } = await axios.get(
`/api/product/${encodeURIComponent(id)}`
);
setProduct(data);
};
if (id) {
fetchProduct();
}
}, [id]);
};

mongodb insertOne inside a loop

I want to insert different collections inside a loop.
I already wrote this and it works once.
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true
});
let insertflowers = async (collectionname,query) => {
try {
await client.connect();
const database = client.db('flowers');
const collection = database.collection(collectionname);
return await collection.insertOne(query);
} finally {
await client.close();
}
}
insertflowers('alocasias',{name:'...'}).catch(console.dir);
What I want to do is put it inside a loop like this.
arrayofflowers.forEach( val => {
let flowerType = ...
insertflowers(flowerType,{name:'...'}).catch(console.dir);
});
But I get the following error
MongoError: Topology is closed, please connect
Thank you for reading
In short remove await client.close();
Check https://docs.mongodb.com/drivers/node/usage-examples/insertMany/ to insert bulk records at once.
You're in race condition so when insertflowers process is running in parallel connection is closed and opening.
So when you try to insert data connection is closed by another call to insertflowers.
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true
});
let connection;
const connect = async () => {
if (!connection) { // return connection if already connected
connection = await client.connect();
}
return connection;
});
let insertflowers = async (collectionname,query) => {
try {
const conn = await connect();
const database = conn.db('flowers');
const collection = database.collection(collectionname);
return await collection.insertOne(query);
} finally {
console.log('insertflowers completed');
// await client.close(); remove this
}
}
Another option - Not a good idea though
Make insertflowers is run the sync.

Jest mock mongoose.startSession() throws error

i'm implemented transaction in the post method. it was work fine. But now I have to update unit test case for that method. I tried to mock startSession() and startTransaction() to check toHaveBeenCalled.But while running test case i got like MongooseError: Connection 0 was disconnected when calling startSession``. I am new to that so i don't know how to mock that?.
Method:
static post = (funcCall: Promise<Document>) => async (response: Response, fields?: string[]) => {
const session = await startSession();
session.startTransaction();
try {
const dbResponse = await funcCall; // model.save(request.body)
// commit the changes if everything was successful
await session.commitTransaction();
success(pick(dbResponse, fields ? fields : ['_id']), 201)(response);
} catch (error) {
// this will rollback any changes made in the database
await session.abortTransaction();
throwError(error);
} finally {
// ending the session
session.endSession();
}
};
My Test case:
it('should perform post when valid parameters is passed.', async () => {
// Preparing
const mockSaveReturn = {
_id: objectID,
test_name: 'sample',
};
jest.spyOn(mongoose, 'startSession')
const spySave = jest.spyOn(modelPrototype.prototype, 'save').mockReturnValueOnce(mockSaveReturn);
const document = new modelPrototype(mockSaveReturn);
// Executing
await post(document.save())(response as Response);
expect(response.send).toHaveBeenCalledWith({ _id: '54759eb3c090d83494e2d804' });
expect(spySave).toHaveBeenCalled();
// Cleaning
spySave.mockClear();
});

Error when combining Express Router with Massive.js db call

When making an async/await call to database from an express router to postgres db via massive.js instance, the correct response from db is received, but the router apparently returns before async function finishes; therefore, the test invocation returns undefined. From the console out (below), it seems clear that the async function is not waited for >_<
Is wrapping the router in order to pass the app instance causing the issue?
app.js
const app = express();
const massiveInstance = require("./db_connect");
const routes = require("./routes");
const PORT = 3001;
const server = massiveInstance.then(db => {
// Add db into our app object
app.set("db", db);
app.use("/api", routes(app));
app.listen(PORT, () => {
console.log("Server listening on " + PORT);
});
});
routes.js
const router = require("express").Router();
const { countRegions } = require("./db_queries");
const routes = app => {
const db = app.get("db");
router.get("/regions/count", async (request, response) => {
try {
const total = await countRegions(db);
console.log(`There are ${total} regions.`);
response.send(`There are ${total} regions.`);
} catch (err) {
console.error(err);
}
});
return router;
};
module.exports = routes;
db_queries.js
const countRegions = db => {
db.regions.count().then(total => {
console.log(`db has ${total} count for regions`);
return total;
});
};
module.exports = {
countRegions,
};
console output
Server listening on 3001
There are undefined regions.
db has 15 count for regions
You are not returning a promise returned by then in countRegions method.
So you should add return in your code like this
const countRegions = db => {
//here
return db.regions.count().then(total => {
console.log(`db has ${total} count for regions`);
return total;
});
};
or simply do,
return db.regions.count();