@ -69,6 +69,7 @@ function Datastore (options) {
// binary is always well-balanced
// binary is always well-balanced
this . indexes = { } ;
this . indexes = { } ;
this . indexes . _id = new Index ( { fieldName : '_id' , unique : true } ) ;
this . indexes . _id = new Index ( { fieldName : '_id' , unique : true } ) ;
this . ttlIndexes = { } ;
// Queue a load of the database right away and call the onload handler
// Queue a load of the database right away and call the onload handler
// By default (no onload handler), if there is an error there, no operation will be possible so warn the user by throwing an exception
// By default (no onload handler), if there is an error there, no operation will be possible so warn the user by throwing an exception
@ -115,7 +116,7 @@ Datastore.prototype.resetIndexes = function (newData) {
* @ param { String } options . fieldName
* @ param { String } options . fieldName
* @ param { Boolean } options . unique
* @ param { Boolean } options . unique
* @ param { Boolean } options . sparse
* @ param { Boolean } options . sparse
* @ param { Number } options . expireAfterSeconds - Optional , if set this index becomes a TTL index
* @ param { Number } options . expireAfterSeconds - Optional , if set this index becomes a TTL index ( only works on Date fields , not arrays of Date )
* @ param { Function } cb Optional callback , signature : err
* @ param { Function } cb Optional callback , signature : err
* /
* /
Datastore . prototype . ensureIndex = function ( options , cb ) {
Datastore . prototype . ensureIndex = function ( options , cb ) {
@ -132,6 +133,7 @@ Datastore.prototype.ensureIndex = function (options, cb) {
if ( this . indexes [ options . fieldName ] ) { return callback ( null ) ; }
if ( this . indexes [ options . fieldName ] ) { return callback ( null ) ; }
this . indexes [ options . fieldName ] = new Index ( options ) ;
this . indexes [ options . fieldName ] = new Index ( options ) ;
if ( options . expireAfterSeconds !== undefined ) { this . ttlIndexes [ options . fieldName ] = options . expireAfterSeconds ; } // With this implementation index creation is not necessary to ensure TTL but we stick with MongoDB's API here
try {
try {
this . indexes [ options . fieldName ] . insert ( this . getAllData ( ) ) ;
this . indexes [ options . fieldName ] . insert ( this . getAllData ( ) ) ;
@ -247,13 +249,18 @@ Datastore.prototype.updateIndexes = function (oldDoc, newDoc) {
* Returned candidates will be scanned to find and remove all expired documents
* Returned candidates will be scanned to find and remove all expired documents
*
*
* @ param { Query } query
* @ param { Query } query
* @ param { Boolean } dontExpireStaleDocs Optional , defaults to false , if true don 't remove stale docs. Useful for the remove function which shouldn' t be impacted by expirations
* @ param { Function } callback Signature err , docs
* @ param { Function } callback Signature err , docs
* /
* /
Datastore . prototype . getCandidates = function ( query , callback ) {
Datastore . prototype . getCandidates = function ( query , dontExpireStaleDocs , callback ) {
var indexNames = Object . keys ( this . indexes )
var indexNames = Object . keys ( this . indexes )
, self = this
, self = this
, usableQueryKeys ;
, usableQueryKeys ;
if ( typeof dontExpireStaleDocs === 'function' ) {
callback = dontExpireStaleDocs ;
dontExpireStaleDocs = false ;
}
async . waterfall ( [
async . waterfall ( [
// STEP 1: get candidates list by checking indexes from most to least frequent usecase
// STEP 1: get candidates list by checking indexes from most to least frequent usecase
@ -299,7 +306,28 @@ Datastore.prototype.getCandidates = function (query, callback) {
}
}
// STEP 2: remove all expired documents
// STEP 2: remove all expired documents
, function ( docs ) {
, function ( docs ) {
return callback ( null , docs ) ;
if ( dontExpireStaleDocs ) { return callback ( null , docs ) ; }
var expiredDocsIds = [ ] , validDocs = [ ] , ttlIndexesFieldNames = Object . keys ( self . ttlIndexes ) ;
docs . forEach ( function ( doc ) {
var valid = true ;
ttlIndexesFieldNames . forEach ( function ( i ) {
if ( doc [ i ] !== undefined && util . isDate ( doc [ i ] ) && Date . now ( ) > doc [ i ] . getTime ( ) + self . ttlIndexes [ i ] * 1000 ) {
valid = false ;
}
} ) ;
if ( valid ) { validDocs . push ( doc ) ; } else { expiredDocsIds . push ( doc . _id ) ; }
} ) ;
async . eachSeries ( expiredDocsIds , function ( _id , cb ) {
self . _remove ( { _id : _id } , { } , function ( err ) {
if ( err ) { return callback ( err ) ; }
return cb ( ) ;
} ) ;
} , function ( err ) {
return callback ( null , validDocs ) ;
} ) ;
} ] ) ;
} ] ) ;
} ;
} ;
@ -629,7 +657,7 @@ Datastore.prototype._remove = function (query, options, cb) {
callback = cb || function ( ) { } ;
callback = cb || function ( ) { } ;
multi = options . multi !== undefined ? options . multi : false ;
multi = options . multi !== undefined ? options . multi : false ;
this . getCandidates ( query , function ( err , candidates ) {
this . getCandidates ( query , true , function ( err , candidates ) {
if ( err ) { return callback ( err ) ; }
if ( err ) { return callback ( err ) ; }
try {
try {