Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

indexeddb.get(open_revs): don't re-fetch docs #8801

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
151 changes: 149 additions & 2 deletions packages/node_modules/pouchdb-adapter-indexeddb/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
'use strict';
import { changesHandler } from 'pouchdb-utils';
import {
adapterFun,
changesHandler,
processAttachments,
processRevsRevsInfo,
} from 'pouchdb-utils';

import {
collectLeaves,
collectConflicts,
isDeleted,
latest as getLatest,
} from 'pouchdb-merge';

import {
INVALID_ID,
UNKNOWN_ERROR,
INVALID_REV,
createError
} from 'pouchdb-errors';

import setup from './setup';

Expand All @@ -16,7 +35,7 @@ import destroy from './destroy';
import {query, viewCleanup} from './find';
import purge from './purge';

import { DOC_STORE } from './util';
import { DOC_STORE, idbError } from './util';

var ADAPTER_NAME = 'indexeddb';

Expand All @@ -26,6 +45,10 @@ var idbChanges = new changesHandler();
// A shared list of database handles
var openDatabases = {};

// Current implementation of _getAttachment() means we DO NOT need a database
// transaction, so the "ctx" does not need to be passed to processAttachments().
const NO_CTX_REQUIRED = null;

function IndexeddbPouch(dbOpts, callback) {

if (dbOpts.view_adapter) {
Expand Down Expand Up @@ -153,6 +176,130 @@ function IndexeddbPouch(dbOpts, callback) {

api._purge = $t(purge, [DOC_STORE], 'readwrite');

api.get = adapterFun('get', function (id, opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
opts = opts || {};
if (typeof id !== 'string') {
return cb(createError(INVALID_ID));
}

if (opts.open_revs) {
let leaves;
let metadata;

const finishOpenRevs = async () => {
var result = [];
/* istanbul ignore if */
if (!leaves.length) {
return cb(null, result);
}

const processing = [];
const seen = new Map();
for (let i=leaves.length-1; i>=0; --i) {
const leaf = leaves[i];

const rev = opts.latest ? getLatest(leaf, metadata) : leaf;
if (seen.has(rev)) {
continue;
}
seen.set(rev);

if (!(rev in metadata.revs)) {
result.push({ missing: leaf });
} else {
const doc = metadata.revs[rev].data;
doc._id = metadata.id;
doc._rev = rev;

if (isDeleted(metadata, rev)) {
doc._deleted = true;
}

if (opts.revs) {
processRevsRevsInfo(id, doc, metadata.rev_tree, opts);
}

processing.push(new Promise((resolve, reject) => {
const cb = (err, res) => err ? reject(err) : resolve(res);
processAttachments(api, metadata, doc, opts, NO_CTX_REQUIRED, cb);
}));

result.push({ ok: doc });
}
}
try {
await Promise.all(processing);
cb(null, result);
} catch (err) {
cb(err);
}
};

api._openTransactionSafely([DOC_STORE], 'readonly', function (err, txn) {
txn.onabort = function () {
cb(createError(UNKNOWN_ERROR, 'transaction was aborted'));
};
txn.ontimeout = idbError(cb);
var req = txn.objectStore(DOC_STORE).get(id);
req.onsuccess = function (e) {
metadata = e.target.result;
if (opts.open_revs === "all") {
leaves = collectLeaves(metadata.rev_tree).map(function (leaf) {
return leaf.rev;
});
finishOpenRevs();
} else {
if (Array.isArray(opts.open_revs)) {
leaves = opts.open_revs;
for (var i = 0; i < leaves.length; i++) {
var l = leaves[i];
// looks like it's the only thing couchdb checks
// TODO replace with !isValidRev(l);
if (!(typeof (l) === "string" && /^\d+-/.test(l))) {
return cb(createError(INVALID_REV));
}
}
finishOpenRevs();
} else {
return cb(createError(UNKNOWN_ERROR, 'function_clause'));
}
}
};
});
return; // open_revs does not like other options
}

return api._get(id, opts, (err, result) => {
if (err) {
err.docId = id;
return cb(err);
}

var doc = result.doc;
var metadata = result.metadata;
var ctx = result.ctx;

if (opts.conflicts) {
var conflicts = collectConflicts(metadata);
if (conflicts.length) {
doc._conflicts = conflicts;
}
}

if (isDeleted(metadata, doc._rev)) {
doc._deleted = true;
}

processRevsRevsInfo(id, doc, metadata.rev_tree, opts);

processAttachments(api, metadata, doc, opts, ctx, cb);
});
}).bind(api);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look like code you would write... is it copied from somewhere? If so, can it be reused?

Copy link
Member Author

@alxndrsn alxndrsn Oct 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC it mostly came from adapter.js in pouchd-core. You're right that there's probably more code that could be shared.


// TODO: this setTimeout seems nasty, if its needed lets
// figure out / explain why
setTimeout(function () {
Expand Down
89 changes: 8 additions & 81 deletions packages/node_modules/pouchdb-core/src/adapter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
rev,
guardedConsole,
isRemote
isRemote,
processAttachments,
processRevsRevsInfo,
} from 'pouchdb-utils';
import EventEmitter from 'events';
import Changes from './changes';
Expand All @@ -17,7 +19,6 @@ import {
import {
traverseRevTree,
collectLeaves,
rootToLeaf,
collectConflicts,
isDeleted,
isLocalId,
Expand Down Expand Up @@ -555,6 +556,7 @@ class AbstractPouchDB extends EventEmitter {
for (var i = 0; i < leaves.length; i++) {
var l = leaves[i];
// looks like it's the only thing couchdb checks
// TODO replace with !isValidRev(l);
Copy link
Member Author

@alxndrsn alxndrsn Oct 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function needs to be created.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now exists...

if (!(typeof (l) === "string" && /^\d+-/.test(l))) {
return cb(createError(INVALID_REV));
}
Expand Down Expand Up @@ -589,89 +591,14 @@ class AbstractPouchDB extends EventEmitter {
}

if (opts.revs || opts.revs_info) {
var splittedRev = doc._rev.split('-');
var revNo = parseInt(splittedRev[0], 10);
var revHash = splittedRev[1];

var paths = rootToLeaf(metadata.rev_tree);
var path = null;

for (var i = 0; i < paths.length; i++) {
var currentPath = paths[i];
var hashIndex = currentPath.ids.map(function (x) { return x.id; })
.indexOf(revHash);
var hashFoundAtRevPos = hashIndex === (revNo - 1);

if (hashFoundAtRevPos || (!path && hashIndex !== -1)) {
path = currentPath;
}
}

/* istanbul ignore if */
if (!path) {
err = new Error('invalid rev tree');
err.docId = id;
try {
processRevsRevsInfo(id, doc, metadata.rev_tree, opts);
} catch (err) {
return cb(err);
}

var indexOfRev = path.ids.map(function (x) { return x.id; })
.indexOf(doc._rev.split('-')[1]) + 1;
var howMany = path.ids.length - indexOfRev;
path.ids.splice(indexOfRev, howMany);
path.ids.reverse();

if (opts.revs) {
doc._revisions = {
start: (path.pos + path.ids.length) - 1,
ids: path.ids.map(function (rev) {
return rev.id;
})
};
}
if (opts.revs_info) {
var pos = path.pos + path.ids.length;
doc._revs_info = path.ids.map(function (rev) {
pos--;
return {
rev: pos + '-' + rev.id,
status: rev.opts.status
};
});
}
}

if (opts.attachments && doc._attachments) {
var attachments = doc._attachments;
var count = Object.keys(attachments).length;
if (count === 0) {
return cb(null, doc);
}
Object.keys(attachments).forEach((key) => {
this._getAttachment(doc._id, key, attachments[key], {
binary: opts.binary,
metadata: metadata,
ctx: ctx
}, function (err, data) {
var att = doc._attachments[key];
att.data = data;
delete att.stub;
delete att.length;
if (!--count) {
cb(null, doc);
}
});
});
} else {
if (doc._attachments) {
for (var key in doc._attachments) {
/* istanbul ignore else */
if (Object.prototype.hasOwnProperty.call(doc._attachments, key)) {
doc._attachments[key].stub = true;
}
}
}
cb(null, doc);
}
processAttachments(this, metadata, doc, opts, ctx, cb);
});
}).bind(this);

Expand Down
4 changes: 4 additions & 0 deletions packages/node_modules/pouchdb-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import once from './once';
import parseDdocFunctionName from './parseDdocFunctionName';
import parseUri from './parseUri';
import pick from './pick';
import processAttachments from './processAttachments';
import processRevsRevsInfo from './processRevsRevsInfo';
import scopeEval from './scopeEval';
import toPromise from './toPromise';
import upsert from './upsert';
Expand Down Expand Up @@ -50,6 +52,8 @@ export {
parseDdocFunctionName,
parseUri,
pick,
processAttachments,
processRevsRevsInfo,
rev,
scopeEval,
toPromise,
Expand Down
41 changes: 41 additions & 0 deletions packages/node_modules/pouchdb-utils/src/processAttachments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

/**
* Process doc._attachments as requested in opts.
*/

function processAttachments(api, metadata, doc, opts, ctx, cb) {
if (opts.attachments && doc._attachments) {
const attachments = doc._attachments;
let count = Object.keys(attachments).length;
if (count === 0) {
return cb(null, doc);
}
Object.keys(attachments).forEach((key) => {
api._getAttachment(doc._id, key, attachments[key], {
binary: opts.binary,
metadata: metadata,
ctx: ctx,
}, function (err, data) {
const att = doc._attachments[key];
att.data = data;
delete att.stub;
delete att.length;
if (!--count) {
cb(null, doc);
}
});
});
} else {
if (doc._attachments) {
for (const key in doc._attachments) {
/* istanbul ignore else */
if (Object.prototype.hasOwnProperty.call(doc._attachments, key)) {
doc._attachments[key].stub = true;
}
}
}
cb(null, doc);
}
}

export default processAttachments;