WIP instead of having one function that can handle both async and callback signatures, let's implement a new Async suite of functions, and use callbackify to shim the old interface

pull/11/head
Timothée Rebours 3 years ago
parent 5a4859c3ba
commit f9e4c525e4
  1. 123
      lib/datastore.js

@ -1,4 +1,5 @@
const { EventEmitter } = require('events') const { EventEmitter } = require('events')
const { callbackify } = require('util')
const async = require('async') const async = require('async')
const Cursor = require('./cursor.js') const Cursor = require('./cursor.js')
const customUtils = require('./customUtils.js') const customUtils = require('./customUtils.js')
@ -267,17 +268,52 @@ class Datastore extends EventEmitter {
* @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 {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, candidates * @param {Function} callback Signature err, candidates
*/ */
async getCandidates (query, dontExpireStaleDocs, callback) { getCandidates (query, dontExpireStaleDocs, callback) {
const validDocs = []
if (typeof dontExpireStaleDocs === 'function') { if (typeof dontExpireStaleDocs === 'function') {
callback = dontExpireStaleDocs callback = dontExpireStaleDocs
dontExpireStaleDocs = false dontExpireStaleDocs = false
} }
try { callbackify(this.getCandidatesAsync.bind(this))(query, dontExpireStaleDocs, callback)
}
async getCandidatesAsync (query, dontExpireStaleDocs = false) {
const indexNames = Object.keys(this.indexes)
const validDocs = []
const _getCandidates = query => {
// 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
const docs = this._getCandidates(query) // For a basic match
let usableQuery
usableQuery = Object.entries(query)
.filter(([k, v]) =>
!!(typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || isDate(v) || v === null) &&
indexNames.includes(k)
)
.pop()
if (usableQuery) return this.indexes[usableQuery[0]].getMatching(usableQuery[1])
// For a $in match
usableQuery = Object.entries(query)
.filter(([k, v]) =>
!!(query[k] && Object.prototype.hasOwnProperty.call(query[k], '$in')) &&
indexNames.includes(k)
)
.pop()
if (usableQuery) return this.indexes[usableQuery[0]].getMatching(usableQuery[1].$in)
// For a comparison match
usableQuery = Object.entries(query)
.filter(([k, v]) =>
!!(query[k] && (Object.prototype.hasOwnProperty.call(query[k], '$lt') || Object.prototype.hasOwnProperty.call(query[k], '$lte') || Object.prototype.hasOwnProperty.call(query[k], '$gt') || Object.prototype.hasOwnProperty.call(query[k], '$gte'))) &&
indexNames.includes(k)
)
.pop()
if (usableQuery) return this.indexes[usableQuery[0]].getBetweenBounds(usableQuery[1])
// By default, return all the DB data
return this.getAllData()
}
// STEP 1: get candidates list by checking indexes from most to least frequent usecase
const docs = _getCandidates(query)
// STEP 2: remove all expired documents // STEP 2: remove all expired documents
if (!dontExpireStaleDocs) { if (!dontExpireStaleDocs) {
const expiredDocsIds = [] const expiredDocsIds = []
@ -296,12 +332,7 @@ class Datastore extends EventEmitter {
}) })
} }
} else validDocs.push(...docs) } else validDocs.push(...docs)
} catch (error) { return validDocs
if (typeof callback === 'function') callback(error, null)
else throw error
}
if (typeof callback === 'function') callback(null, validDocs)
else return validDocs
} }
/** /**
@ -509,19 +540,30 @@ class Datastore extends EventEmitter {
options = {} options = {}
} }
const callback = cb || (() => {}) const callback = cb || (() => {})
const _callback = (err, res = {}) => {
callback(err, res.numAffected, res.affectedDocuments, res.upsert)
}
callbackify(this._updateAsync.bind(this))(query, updateQuery, options, _callback)
}
async _updateAsync (query, updateQuery, options = {}) {
const multi = options.multi !== undefined ? options.multi : false const multi = options.multi !== undefined ? options.multi : false
const upsert = options.upsert !== undefined ? options.upsert : false const upsert = options.upsert !== undefined ? options.upsert : false
async.waterfall([ // If upsert option is set, check whether we need to insert the doc
cb => { // If upsert option is set, check whether we need to insert the doc if (upsert) {
if (!upsert) return cb()
// Need to use an internal function not tied to the executor to avoid deadlock // Need to use an internal function not tied to the executor to avoid deadlock
const cursor = new Cursor(this, query) const cursor = new Cursor(this, query)
const docs = await new Promise((resolve, reject) => {
cursor.limit(1)._exec((err, docs) => { cursor.limit(1)._exec((err, docs) => {
if (err) return callback(err) if (err) reject(err)
if (docs.length === 1) return cb() else resolve(docs)
else { })
})
if (docs.length !== 1) {
let toBeInserted let toBeInserted
try { try {
@ -531,32 +573,26 @@ class Datastore extends EventEmitter {
} catch (e) { } catch (e) {
// updateQuery contains modifiers, use the find query as the base, // updateQuery contains modifiers, use the find query as the base,
// strip it from all operators and update it according to updateQuery // strip it from all operators and update it according to updateQuery
try {
toBeInserted = model.modify(model.deepCopy(query, true), updateQuery) toBeInserted = model.modify(model.deepCopy(query, true), updateQuery)
} catch (err) {
return callback(err)
}
} }
return this._insert(toBeInserted, (err, newDoc) => { return new Promise((resolve, reject) => {
if (err) return callback(err) this._insert(toBeInserted, (err, newDoc) => {
return callback(null, 1, newDoc, true) if (err) return reject(err)
return resolve({ numAffected: 1, affectedDocuments: newDoc, upsert: true })
}) })
}
}) })
}, }
() => { // Perform the update }
// Perform the update
let numReplaced = 0 let numReplaced = 0
let modifiedDoc let modifiedDoc
const modifications = [] const modifications = []
let createdAt let createdAt
this.getCandidates(query, (err, candidates) => { const candidates = await this.getCandidatesAsync(query)
if (err) return callback(err)
// Preparing update (if an error is thrown here neither the datafile nor // Preparing update (if an error is thrown here neither the datafile nor
// the in-memory indexes are affected) // the in-memory indexes are affected)
try {
for (const candidate of candidates) { for (const candidate of candidates) {
if (model.match(candidate, query) && (multi || numReplaced === 0)) { if (model.match(candidate, query) && (multi || numReplaced === 0)) {
numReplaced += 1 numReplaced += 1
@ -569,32 +605,25 @@ class Datastore extends EventEmitter {
modifications.push({ oldDoc: candidate, newDoc: modifiedDoc }) modifications.push({ oldDoc: candidate, newDoc: modifiedDoc })
} }
} }
} catch (err) {
return callback(err)
}
// Change the docs in memory // Change the docs in memory
try {
this.updateIndexes(modifications) this.updateIndexes(modifications)
} catch (err) {
return callback(err)
}
// Update the datafile // Update the datafile
const updatedDocs = modifications.map(x => x.newDoc) const updatedDocs = modifications.map(x => x.newDoc)
await new Promise((resolve, reject) => {
this.persistence.persistNewState(updatedDocs, err => { this.persistence.persistNewState(updatedDocs, err => {
if (err) return callback(err) if (err) return reject(err)
if (!options.returnUpdatedDocs) { else resolve()
return callback(null, numReplaced) })
} else { })
if (!options.returnUpdatedDocs) return { numAffected: numReplaced }
else {
let updatedDocsDC = [] let updatedDocsDC = []
updatedDocs.forEach(doc => { updatedDocsDC.push(model.deepCopy(doc)) }) updatedDocs.forEach(doc => { updatedDocsDC.push(model.deepCopy(doc)) })
if (!multi) updatedDocsDC = updatedDocsDC[0] if (!multi) updatedDocsDC = updatedDocsDC[0]
return callback(null, numReplaced, updatedDocsDC) return { numAffected: numReplaced, affectedDocuments: updatedDocsDC }
} }
})
})
}])
} }
update () { update () {

Loading…
Cancel
Save