blob: ed20124acdc351fb40359d6c928d539618d6e293 [file]
import { createError, MISSING_DOC, REV_CONFLICT } from 'pouchdb-errors';
import updateDoc from './updateDoc';
import {
isDeleted,
isLocalId,
merge,
winningRev as calculateWinningRev
} from 'pouchdb-merge';
function rootIsMissing(docInfo) {
return docInfo.metadata.rev_tree[0].ids[1].status === 'missing';
}
function insertDoc(docInfo, results, resultsIdx, newEdits, wasDelete, writeDoc, callback) {
// Cant insert new deleted documents
var winningRev = calculateWinningRev(docInfo.metadata);
var deleted = isDeleted(docInfo.metadata, winningRev);
if (wasDelete && deleted) {
results[resultsIdx] = createError(MISSING_DOC, 'deleted');
return callback();
}
// 4712 - detect whether a new document was inserted with a _rev
var inConflict = newEdits && rootIsMissing(docInfo);
if (inConflict) {
var err = createError(REV_CONFLICT);
results[resultsIdx] = err;
return callback();
}
var delta = deleted ? 0 : 1;
writeDoc(docInfo, winningRev, deleted, deleted, false,
delta, resultsIdx, callback);
}
function mergeDoc(currentDoc, results, resultsIdx, revLimit, newEdits, wasDelete, writeDoc, docWritten) {
// Ensure stemming applies to new writes as well
var merged = merge([], currentDoc.metadata.rev_tree[0], revLimit);
currentDoc.metadata.rev_tree = merged.tree;
currentDoc.stemmedRevs = merged.stemmedRevs || [];
insertDoc(currentDoc, results, resultsIdx, newEdits, wasDelete, writeDoc, docWritten);
}
function mergeOrUpdateDoc(id, fetchedDocs, currentDoc, results, resultsIdx, revLimit, newEdits,
wasDelete, writeDoc, docWritten) {
if (fetchedDocs.has(id)) {
updateDoc(revLimit, fetchedDocs.get(id), currentDoc, results,
resultsIdx, docWritten, writeDoc, newEdits);
} else {
mergeDoc(currentDoc, results, resultsIdx, revLimit, newEdits, wasDelete, writeDoc, docWritten);
}
}
function processsMultipleDocsWithSameId(id, docs, fetchedDocs, results, revLimit, newEdits,
wasDelete, writeDoc, checkAllDocsDone) {
var numDone = 0;
function docWritten() {
if (++numDone < docs.length) {
nextDoc();
} else {
checkAllDocsDone();
}
}
function nextDoc() {
var value = docs[numDone];
var currentDoc = value[0];
var resultsIdx = value[1];
mergeOrUpdateDoc(id, fetchedDocs, currentDoc, results, resultsIdx, revLimit, newEdits,
wasDelete, writeDoc, docWritten);
}
nextDoc();
}
function afterLocalPutOrRemove(i, results, checkAllDocsDone) {
return function (err, res) {
results[i] = err || res;
checkAllDocsDone();
};
}
function processDocs(revLimit, docInfos, api, fetchedDocs, tx, results,
writeDoc, opts, callback) {
// Default to 1000 locally
revLimit = revLimit || 1000;
var newEdits = opts.new_edits;
var wasDelete = 'was_delete' in opts;
var idsToDocs = {};
var docsDone = 0;
var docsToDo = docInfos.length;
function checkAllDocsDone() {
if (++docsDone === docsToDo && callback) {
callback();
}
}
for (var i = 0, len = docInfos.length; i < len ; i++) {
var currentDocInfo = docInfos[i];
if (currentDocInfo._id && isLocalId(currentDocInfo._id)) {
var fun = currentDocInfo._deleted ? '_removeLocal' : '_putLocal';
api[fun](currentDocInfo, {ctx: tx}, afterLocalPutOrRemove(i, results, checkAllDocsDone));
} else {
var currentDocId = currentDocInfo.metadata.id;
var currentKey = '$' + currentDocId;
var currentValue = idsToDocs[currentKey];
if (currentValue) {
docsToDo--; // duplicate
currentValue.push([currentDocInfo, i]);
} else {
idsToDocs[currentKey] = [[currentDocInfo, i]];
}
}
}
// in the case of new_edits, the user can provide multiple docs
// with the same id. these need to be processed sequentially
for (var thisKey in idsToDocs) {
if (idsToDocs.hasOwnProperty(thisKey)) {
var id = thisKey.substring(1); // cut off initial '$'
var docs = idsToDocs[thisKey];
if (docs.length === 1) { // fast case
var thisDoc = docs[0];
mergeOrUpdateDoc(id, fetchedDocs, thisDoc[0], results, thisDoc[1], revLimit, newEdits,
wasDelete, writeDoc, checkAllDocsDone);
} else { // slow case
processsMultipleDocsWithSameId(id, docs, fetchedDocs, results, revLimit, newEdits,
wasDelete, writeDoc, checkAllDocsDone);
}
}
}
}
export default processDocs;