mongoose.connection.collections.collection.drop() throws error every other time - node.js

I am setting up testing using Jest for an Node/Express/Mongo project. I have tried to write a function to clear collections so each test starts with a clean slate:
const clearCollection = (collectionName, done) => {
const collection = mongoose.connection.collections[collectionName]
collection.drop(err => {
if (err) throw new Error(err)
else done()
)
}
beforeEach(done => {
clearCollection('users', done)
})
And another try, with promises:
const clearCollection = collectionName => {
const collection = mongoose.connection.collections[collectionName]
return collection.drop()
}
beforeEach(async () => {
await clearCollection('users')
})
The problem is that it they both alternate between working and throwing an error. Every time I save the file, it either works perfectly, or throws an error, alternating each time. The errors are always either one of:
MongoError: cannot perform operation: a background operation is currently running for collection auth_test.users
MongoError: ns not found
I can get it to work 100% of the time (limited by the stack anyway) by making clearCollection() call itself inside a catch(), but this feels so wrong:
const clearCollection = collectionName => {
const collection = mongoose.connection.collections[collectionName]
return collection.drop()
.catch(() => clearCollection(collectionName))
}

I don't know why mongoose.connection.collections.<collection>.drop() randomly throws errors, but there is a simple way to remove all the documents in Mongoose, which works just fine for resetting the collection before tests:
beforeAll(async () => {
await User.remove({})
})
Works every time.

I was having a similar issue while trying to drop the database in the beginning of the tests and populating the database right after. In the first run, collections would be created; in the next one, I would get an error of 'database is in the process of being dropped.'; ...and it was alternating like this.
I was also using an "in memory" Mongodb, running $ run-rs -v 4.0.0 -s (https://www.npmjs.com/package/run-rs) in a separate terminal window before running my Mocha tests. Also, I have Mongoose 5.2.0 and Mocha 5.1.1 here.
I've found out that Mongoose not necessarily executes the the drop commands immediately. Instead, it will schedule them for when the connection is up.
So, there can be a race condition in which the promise of the drop command is resolved and you move on in the code until reaching the instruction for creating your collections... but the drop command didn't finish running yet, and you'll get the error for the creation the new collections. The drop finish running and for the next time you run your test, your database (or collection) is already dropped, and hence you'll be able to insert the new collections again.
So, this is how I've solved...
Run in the before hook:
test.dropDb = function(done) {
this.timeout(0)
let database = your_MongoDB_URI
mongoose.connect(database, function (err) {
if (err) throw err;
mongoose.connection.db.dropDatabase(function(err, result){
console.log('database dropping was scheduled');
});
})
mongoose.connection.on('connected', function() {
setTimeout(function(){ done() }, 3000);
});
}
Run in an nested before hook
test.createDb = function(done) {
this.timeout(0)
let createDb = require('./create-db.js')
createDb() // here I call all my MyCollections.insertMany(myData)...
.then( function() {
done()
})
.catch( function(err) {
assert( null == err)
});
}
Run in the after hook
test.afterAll = function(done) {
mongoose.connection.close()
.then( function() {
done()
})
}
I don't know why I had to explicitly close the connection in the after hook. I guess it has something to do with the way run-rs is programmed, but I didn't look into it. I just know that, in my case, closing the connection after the test was mandatory to get the expected behavior.

Related

Why does Node Js mongoose document deletion does not execute without await?

So i am novice at Node.js, and have been checking out its mongoose package. I have a mongodb collection with a document with field name = "Test Entry"
I have tried executing a .findOneAndDelete() method call on a model wrapped within a timeout, wrapped within a function.
One issue that baffles me is why this piece of code
function deleteEntry() {
setTimeout(function () {
Test_Model.findOneAndDelete({
name: "Test Entry",
});
}, 5000);
}
deleteEntry();
does not delete the entry within mongodb
whereas
function deleteEntry() {
setTimeout(async function () {
await Test_Model.findOneAndDelete({
name: "Test Entry",
});
}, 5000);
}
deleteEntry();
this one does.
will the mongoose method not execute unless there is a callback made of some kind. No matter if it's for vain?
There were mentions of something similar in the documentation . Does the actual execution of the command occur only when there is a callback/await/then ?
"Why does not calling this operation NOT trigger the task?" - I think MongoDB driver check if you passed a callback (as argument) or not, If you didn't pass callback, Instead, It return Promise. So no matter what, You operation will get executed.
Also you should handle Error in setTimeout function, Because It don't handle any error or error from any async operation.
Try this example:
let delay = ms => new Promise(ok => setTimeout(ok, ms));
async function deleteEntry() {
await delay(5000);
return Test_Model.findOneAndDelete({ name: "Test Entry" });
}
deleteEntry()
.then(result => console.log(result))
.catch(err => console.log(err));

Nodejs Async skipping await

So I'm missing something here. My goal here is to have each of the console.log statements in sequential order, but some reason the runtime is continuing without waiting for my await command.
Here's a simplified example:
# Setup a method
exports.selectAll = async (db) => {
db.run("SELECT * FROM stats", (err, data) => {
if (err) console.error(err);
else {
console.log("Hit1");
return data;
}
});
};
Just a simple process for returning some longer command from the db. However when I go to call this:
## Runtime
(async () => {
const db = await exports.open();
const data = await exports.selectAll(db);
console.log("Hit2");
console.log(data);
console.log("Hit3");
exports.close(db);
})()
I get the following output:
Hit2
undefined
Hit3
sql - connected
Hit1
sql - db closed
Why is the function not waiting for const data = await exports.selectAll(db);?
Quick aside:
I've confirmed its not and issue with db.run, as if I console.log right after the Hit1 I can see the return of data.
Because inside selectAll you aren't returning anything awaitable, and since the db.run call is IO bound, it'll fire the query and hand context back to the thread, meaning the console logs will be processed first (before the DB callback runs).
If the library you are using doesn't support Promises then you'll need to create your own:
exports.selectAll = db => new Promise((resolve, reject) => {
db.run("SELECT * FROM stats", (err, data) => err ? reject(err) : resolve(data));
});

How to handle errors using the Tedious driver with Node

The Problem:
I keep running into issues with typos in my SQL queries and the tedious driver isn't helping much. When there are errors, it does a fine job telling me the type of error and what went wrong, but the stack trace never contains any of my project files. This is making it difficult to debug, especially when I'm making multiple queries in the same function. I'm currently using the mssql library, but experienced the same problem using tedious directly. Plus, mssql isn't mentioned in the error's stack at all, so I figure it's not the problem.
What I've Tried:
Looking for issues on the tedious GitHub page
Added a conn.on('error') listener, which doesn't appear to do anything.
Added process.on() listeners for 'uncaughtException' and 'unhandledRejection'.
Moved the initialization of the ConnectionPool into it's own function to try and catch the error there. No luck.
Some Code:
Here's a barebones example of what I'm using to run the queries:
async function dbConnect() {
const conn = await new ConnectionPool(dbConfig).connect();
conn.on('error', error => {
console.error('*Connection Error:', error);
});
return conn;
}
async function runQuery() {
const conn = await dbConnect();
await conn.request().query(`SELECT Thing FROM Place`);
await conn.close();
}
runQuery()
.then(results => {
console.log(results);
process.exit(0);
})
.catch(error => {
console.error(error);
process.exit(1);
});
The Error:
Hopefully, this is something that's possible. If not, is there a different Node driver for SQL Server that someone's come up with?
Edit: Added an error listener on request
async function dbConnect() {
const conn = await new ConnectionPool(dbConfig).connect();
const request = new Request(conn);
conn.on('error', error => {
console.error('*Connection Error:', error);
});
request.on('error', error => {
console.error('*Request Error:', error);
});
return request;
}
async function runQuery() {
const request = await dbConnect();
await request.query(`SELECT Thing FROM Place`);
}

Close Database Connection (Firebase+Node.js)

I am currently working on Node.js and firebase and I am currently stuck in an issue, where after writing the data in database my program does not end on a terminal, is there a way I can end this.
I guess, maybe I need to end the firebase connection but I don't have an idea how would I do that.
Here is the program,
module.exports=
{
create_firebase_entry: function ()
{
console.log("Firebase Entry")
return new Promise((resolve, reject) => {
var secondaryAppConfig =
{
credential: admin.credential.cert(serviceAccount),
databaseURL: "firebase_databse_url"
};
if (admin.apps.length===0){secondary = admin.initializeApp(secondaryAppConfig, "secondary");}
// Retrieve the database.
var db = secondary.database();
var ref = db.ref("data_records");
var usersRef = ref.child("My_Data");
usersRef.set({Data1:"Trail Data"})
})
}
}
The script opens a connection to the Firebase Realtime Database, and then performs asynchronous read and write operations over that connection. Since the script has no way to know when you're done, you will have to signal this yourself.
The most common way to do this (that I know of) is to call process.exit() when you're done. E.g.
usersRef.set({Data1:"Trail Data"})
.then(function() {
process.exit(0);
});
Also see
How to properly exit firebase-admin nodejs script when all transaction is completed
Exit from a NodeJS script when all asynchronous tasks are done
node process doesn't exit after firebase once
I agree to Frank but I think using process.exit(0); under .finally() block will provide more guarantee. Because when error occur It will never close.
usersRef.set({Data1:"Trail Data"})
.then(result => {
// ...
})
.catch(err => {
// ...
})
.finally(() => {
process.exit(0)
})

How to check if a collection already exists in ArangoDB

Say I have a collection Col1 that already exists in my database. So, doing something like:
var col = db.collection('Col1');
col.save({"name":"something"});
will work perfectly fine.
But if a collection Col2 that doesn't already exist in my database is tried with the same thing i.e
var col = db.collection('Col2');
col.save({"name":"something"})
will work perfectly fine as well. Only that it doesn't exist and will be not shown in my database. Had it thrown some error or stuffs I could have used try and catch statements for result. But since that is off the plate, how do I know if a collection already exists ?
There are two things going on here that may be confusing.
First of all, arangojs (unlike the internal JS API of ArangoDB) is asynchronous for everything that needs to talk to the actual ArangoDB server. Asynchronous functions are marked as "async" in the documentation.
You can pass a node.js-style callback (like the async functions in built-in node.js modules, e.g. fs, http, etc) to these methods. Alternatively you can simply omit the callback and the method will return a promise for the result. You can learn more about how promises work in Mozilla's JavaScript reference documentation (this is not specific to Mozilla -- their reference is just very good and generally correct).
The other thing you're running into is the distinction between collection objects in arangojs and actual collections in ArangoDB. The driver lets you create collection objects for collections regardless of whether they exist yet or not. When trying to use them if the collection doesn't actually exist, you will of course see an error.
var col = db.collection('whatever');
col.create() // create the collection if it doesn't exist
.catch(function () {}) // ignore any errors
.then(function () {
return col.get(); // make sure the collection exists now
})
.then(function () {
return col.save({some: 'data'});
})
.then(function (result) {
// everything went fine
})
.catch(function (e) {
console.error('Something went wrong', e.stack);
});
Or using async/await (if you use Babel or read this answer one year from now):
var col = db.collection('whatever');
try {
await col.create(); // create the collection if it doesn't exist
} catch (e) {} // ignore any errors
try {
await col.get(); // make sure the collection exists now
const result = await col.save({some: 'data'});
// everything went fine
} catch (e) {
console.error('Something went wrong', e.stack);
}
Or using node.js-style callbacks because you're oldschool or really like pyramids:
var col = db.collection('whatever');
col.create(function () { // create the collection if it doesn't exist
// ignore any errors
col.get(function (err) { // make sure the collection exists now
if (err) {
console.error('Something went wrong', err.stack);
return;
}
col.save({some: 'data'}, function (err, result) {
if (err) {
console.error('Something went wrong', err.stack);
return;
}
// everything went fine
});
});
});
col.save does not execute the save operation immediately but returns a promise. So it will always succeed. The solution is to wait for the promise to be resolved and then react upon whether an error occurred or not:
var col = db.collection('Col2');
col.save({"name":"something"}).then(
meta => console.log('Document saved:', meta._rev),
err => { console.error('Failed to save document:', err.errorNum, err.response.body.errorMessage); }
);
https://docs.arangodb.com/3.1/Manual/DataModeling/Collections/DatabaseMethods.html#collection states
returns a single collection or null db._collection(collection-name)
so you can use
var col2 = db._collection('Col2');
if (col2) {
// collection exists
col2.save({"name":"something"});
}
This is old, but there is an exists() function.
Example in typescript/node
const metaResult = db.collection('myCollection');
if(!await metaResult.exists()) {
await db.createCollection('myCollection');
}

Resources