Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if collection exists before dropping in Mongoose

When the collection doesn't exist when drop() is called, an error is thrown:

ns not found

Currently I'm using something like

try {
    await MongooseModel.collection.drop();
} catch (err) {
    if (err.message !== 'ns not found') {
        throw err;
    }
}

which doesn't smell too good. It's not really clear why an error is thrown in the first place, I would expect that drop() will return false if it didn't exist, like it's done in Mongo console.

How can this be done when there is only a reference to Mongoose model, MongooseModel?

I don't have a reference to connection object in the place where I'm doing this, like is suggested in this answer.

like image 634
Estus Flask Avatar asked Sep 02 '17 11:09

Estus Flask


People also ask

How do you check if a collection already exists in MongoDB?

Using the DB Class In order to connect to the database, we just need to specify the database name. If the database is not present, MongoDB will automatically create one. Here, the collectionExists method will return true if the collection exists, false otherwise.

How do you check if a collection exists or not?

The collectionNames method of the native driver's Db object accepts an optional collection name filter as a first parameter to let you check the existence of a collection: db. collectionNames(collName, function(err, names) { console. log('Exists: ', names.

How do you check if a data exist in mongoose?

Mongoose | exists() Function The exists() function returns true if at least one document exists in the database that matches the given filter, and false otherwise.

Which command is used to check the existence of collection in MongoDB?

In MongoDB, we can check the existence of the field in the specified collection using the $exists operator.


1 Answers

Actually you do have the reference. You can mongoose.connection at any time after a connection is established and return the current connection.

You can also simply grab the db from any model instance. So an approach you can use if you don't want to try..catch on errors when the collection does not exist, is to basically use the .listCollections() method from the underlying driver in order to see if the collection you want to access actually exists in the namespace first:

const mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const uri = 'mongodb://localhost/blank',
      options = { useMongoClient: true };

const testSchema = new Schema({},{ strict: false });

const ModelA = mongoose.model('ModelA', testSchema);
const ModelB = mongoose.model('ModelB', testSchema);

(async function() {

  try {

    const conn = await mongoose.connect(uri,options);


    await ModelB.create({ a: 1 });


    for ( let model of [ModelA,ModelB] ) {

      let list = await model.db.db.listCollections({
        name: model.collection.name
      }).toArray()
      //console.log(JSON.stringify(list,undefined,2));

      if ( list.length !== 0 ) {
        await model.collection.drop();
      } else {
        console.log('collection %s does not exist',model.collection.name);
      }

    }


  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

Would demonstrate that the first attempted collection drop does not exist, without throwing an error:

Mongoose: modelbs.insert({ a: 1, _id: ObjectId("59aa9cfe581cca7afac55181"), __v: 0 })
collection modelas does not exist
Mongoose: modelbs.drop()

There are other things you can do such as use .collection() from the underlying driver with { strict: true } to get a collection object first. But again this really only "throws an error". So whilst you would "know" before issuing the .drop() that the collection does not exist, it's still the same try..catch handling required.

So for no errors, check for the presence of the collection first. Provided nothing else is going to remove it of course. But you probably should always error handle just in case.


Personally I do find it simply cleaner to allow the exception to occur and then simply inspect the details to see that the exception being raised is what I expect:

for ( let model of [ModelA,ModelB] ) {
  try {
    await model.collection.drop();
  } catch (e) {
    if (e.code === 26) {
      console.log('namespace %s not found',model.collection.name)
    } else {
      throw e;
    }
  }
}

In this case code: 26 for "Namespace not found", which would be raised in this case or in the case using the native .collection() method with { strict: true } as well. But using .drop() of the existing handle to the collection object is much shorter.

Point being that nothing requires you to actually log the exception, and "inspecting exceptions" ( i.e Expected duplicate key errors in bulk inserts ) is a common practice. Just test for the expected error code, and "if" something else is returned then you raise the exception at a higher level.

As stated earlier, whilst you "can" make the presumption that if you ask the database to list the collection by name and it's in the results then it exists, the safest course is to still catch any exception from the I/O in case there is some other modification in between the "query" and the "operation" to remove.

like image 194
Neil Lunn Avatar answered Oct 21 '22 06:10

Neil Lunn