Skip to content

Commit 5290f04

Browse files
committed
[wip] do not use cached metadata for doc_count and seq
1 parent 656663f commit 5290f04

3 files changed

Lines changed: 78 additions & 35 deletions

File tree

packages/node_modules/pouchdb-adapter-indexeddb/src/bulkDocs.js

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ import { DOC_STORE, META_LOCAL_STORE, idbError } from './util';
2424
import { rewrite, sanitise } from './rewrite';
2525
const sanitisedAttachmentKey = sanitise('_attachments');
2626

27-
export default function (api, req, opts, metadata, dbOpts, idbChanges, callback) {
27+
export default function (api, req, opts, connectionInfo, dbOpts, idbChanges, callback) {
2828

29-
let txn;
29+
let metadata = {};
3030

3131
// TODO: I would prefer to get rid of these globals
3232
let error;
@@ -49,7 +49,7 @@ export default function (api, req, opts, metadata, dbOpts, idbChanges, callback)
4949

5050
// Reads the original doc from the store if available
5151
// As in allDocs with keys option using multiple get calls is the fastest way
52-
function fetchExistingDocs(txn, docs) {
52+
function fetchExistingDocs(txn, metadata, docs) {
5353
let fetched = 0;
5454
const oldDocs = {};
5555

@@ -58,7 +58,7 @@ export default function (api, req, opts, metadata, dbOpts, idbChanges, callback)
5858
oldDocs[e.target.result.id] = e.target.result;
5959
}
6060
if (++fetched === docs.length) {
61-
processDocs(txn, docs, oldDocs);
61+
processDocs(txn, metadata, docs, oldDocs);
6262
}
6363
}
6464

@@ -76,7 +76,7 @@ export default function (api, req, opts, metadata, dbOpts, idbChanges, callback)
7676
});
7777
}
7878

79-
function processDocs(txn, docs, oldDocs) {
79+
function processDocs(txn, metadata, docs, oldDocs) {
8080

8181
docs.forEach(function (doc, i) {
8282
let newDoc;
@@ -116,7 +116,7 @@ export default function (api, req, opts, metadata, dbOpts, idbChanges, callback)
116116
} else {
117117
oldDocs[newDoc.id] = newDoc;
118118
lastWriteIndex = i;
119-
write(txn, newDoc, i);
119+
write(txn, metadata, newDoc, i);
120120
}
121121
});
122122
}
@@ -182,7 +182,7 @@ export default function (api, req, opts, metadata, dbOpts, idbChanges, callback)
182182
return doc;
183183
}
184184

185-
function write(txn, doc, i) {
185+
function write(txn, metadata, doc, i) {
186186

187187
// We copy the data from the winning revision into the root
188188
// of the document so that it can be indexed
@@ -287,7 +287,7 @@ export default function (api, req, opts, metadata, dbOpts, idbChanges, callback)
287287
rev: '0-0'
288288
};
289289
};
290-
updateSeq(i);
290+
updateSeq(txn, metadata, i);
291291
return;
292292
}
293293

@@ -298,11 +298,11 @@ export default function (api, req, opts, metadata, dbOpts, idbChanges, callback)
298298
id: doc.id,
299299
rev: writtenRev
300300
};
301-
updateSeq(i);
301+
updateSeq(txn, metadata, i);
302302
};
303303
}
304304

305-
function updateSeq(i) {
305+
function updateSeq(txn, metadata, i) {
306306
if (i === lastWriteIndex) {
307307
txn.objectStore(META_LOCAL_STORE).put(metadata);
308308
}
@@ -320,12 +320,12 @@ export default function (api, req, opts, metadata, dbOpts, idbChanges, callback)
320320
} catch (e) {
321321
return Promise.reject(createError(BAD_ARG, 'Attachment is not a valid base64 string'));
322322
}
323-
if (metadata.idb_attachment_format === 'binary') {
323+
if (connectionInfo.idb_attachment_format === 'binary') {
324324
attachment.data = binStringToBlobOrBuffer(binData, attachment.content_type);
325325
}
326326
} else {
327327
binData = attachment.data;
328-
if (metadata.idb_attachment_format === 'base64') {
328+
if (connectionInfo.idb_attachment_format === 'base64') {
329329
// TODO could run these in parallel, if we cared
330330
return new Promise(resolve => {
331331
blufferToBase64(attachment.data, function (b64) {
@@ -395,13 +395,11 @@ export default function (api, req, opts, metadata, dbOpts, idbChanges, callback)
395395
// We _could_ check doc ids here, and skip opening DOC_STORE if all docs are local.
396396
// This may marginally slow things down for local docs. It seems pragmatic to keep
397397
// the code simple and optimise for calls to bulkDocs() which include non-local docs.
398-
api._openTransactionSafely([DOC_STORE, META_LOCAL_STORE], 'readwrite', function (err, _txn) {
398+
api._openTransactionSafely([DOC_STORE, META_LOCAL_STORE], 'readwrite', function (err, txn) {
399399
if (err) {
400400
return callback(err);
401401
}
402402

403-
txn = _txn;
404-
405403
txn.onabort = function () {
406404
callback(error || createError(UNKNOWN_ERROR, 'transaction was aborted'));
407405
};
@@ -412,8 +410,11 @@ export default function (api, req, opts, metadata, dbOpts, idbChanges, callback)
412410
callback(null, results);
413411
};
414412

415-
// We would like to use promises here, but idb sucks
416-
fetchExistingDocs(txn, docs);
413+
const metaStore = txn.objectStore(META_LOCAL_STORE);
414+
metaStore.get(META_LOCAL_STORE).onsuccess = function (e) {
415+
const metadata = e.target.result;
416+
fetchExistingDocs(txn, metadata, docs);
417+
};
417418
});
418419
}).catch(function (err) {
419420
callback(err);

packages/node_modules/pouchdb-adapter-indexeddb/src/index.js

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,29 @@ function IndexeddbPouch(dbOpts, callback) {
3434
}
3535

3636
const api = this;
37-
let metadata = {};
37+
38+
// When the database is opened, we cache the "static" metadata fields i.e.
39+
// those that do not change when docs are updated. These are needed for some
40+
// async operations (e.g. preprocessing attachments) that cannot be performed
41+
// in the middle of a transaction due to the nature of the IndexedDB API, so
42+
// they need to know these settings without having to read from the object
43+
// store. "Dynamic" values like `doc_count` and `seq` must always be read
44+
// from the object store so that they are not stale.
45+
const CACHED_INFO_KEYS = ['db_uuid', 'idb_attachment_format'];
46+
const connectionInfo = {};
47+
48+
function updateConnectionInfo(metadata) {
49+
for (const key of CACHED_INFO_KEYS) {
50+
connectionInfo[key] = metadata[key];
51+
}
52+
}
3853

3954
// Wrapper that gives you an active DB handle. You probably want $t.
4055
const $ = function (fun) {
4156
return function () {
4257
const args = Array.prototype.slice.call(arguments);
4358
setup(openDatabases, api, dbOpts).then(function (res) {
44-
metadata = res.metadata;
59+
updateConnectionInfo(res.metadata);
4560
args.unshift(res.idb);
4661
fun.apply(api, args);
4762
}).catch(function (err) {
@@ -60,9 +75,8 @@ function IndexeddbPouch(dbOpts, callback) {
6075
const args = Array.prototype.slice.call(arguments);
6176

6277
return setup(openDatabases, api, dbOpts).then(function (res) {
63-
metadata = res.metadata;
78+
updateConnectionInfo(res.metadata);
6479
args.unshift(res.idb);
65-
6680
return fun.apply(api, args);
6781
});
6882
};
@@ -78,7 +92,7 @@ function IndexeddbPouch(dbOpts, callback) {
7892
const args = Array.prototype.slice.call(arguments);
7993
const txn = {};
8094
setup(openDatabases, api, dbOpts).then(function (res) {
81-
metadata = res.metadata;
95+
updateConnectionInfo(res.metadata);
8296
txn.txn = res.idb.transaction(stores, mode);
8397
}).catch(function (err) {
8498
console.error('Failed to establish transaction safely');
@@ -97,29 +111,44 @@ function IndexeddbPouch(dbOpts, callback) {
97111
}, stores, mode)(callback);
98112
};
99113

114+
api._getMetadata = $t(function (txn, cb) {
115+
const metaStore = txn.txn.objectStore(META_LOCAL_STORE);
116+
metaStore.get(META_LOCAL_STORE).onsuccess = function (e) {
117+
cb(null, e.target.result);
118+
};
119+
}, [META_LOCAL_STORE]);
120+
100121
api._remote = false;
101122
api.type = function () { return ADAPTER_NAME; };
102123

103-
api._id = $(function (_, cb) {
104-
cb(null, metadata.db_uuid);
105-
});
124+
api._id = function (cb) {
125+
api._getMetadata(function (err, metadata) {
126+
cb(null, metadata.db_uuid);
127+
});
128+
};
106129

107-
api._info = $(function (_, cb) {
108-
return info(metadata, cb);
109-
});
130+
api._info = function (cb) {
131+
api._getMetadata(function (err, metadata) {
132+
return info(metadata, cb);
133+
});
134+
};
110135

111136
api._get = $t(get, [DOC_STORE]);
112137
api._getLocal = $t(function (txn, id, callback) {
113138
return getLocal(txn, id, api, callback);
114139
}, [META_LOCAL_STORE]);
115140

116141
api._bulkDocs = $(function (_, req, opts, callback) {
117-
bulkDocs(api, req, opts, metadata, dbOpts, idbChanges, callback);
142+
bulkDocs(api, req, opts, connectionInfo, dbOpts, idbChanges, callback);
118143
});
119144

120145
api._allDocs = $t(function (txn, opts, cb) {
121-
allDocs(txn, metadata, opts, cb);
122-
}, [DOC_STORE]);
146+
const metaStore = txn.txn.objectStore(META_LOCAL_STORE);
147+
metaStore.get(META_LOCAL_STORE).onsuccess = function (e) {
148+
const metadata = e.target.result;
149+
allDocs(txn, metadata, opts, cb);
150+
};
151+
}, [DOC_STORE, META_LOCAL_STORE]);
123152

124153
api._getAttachment = getAttachment;
125154

packages/node_modules/pouchdb-adapter-indexeddb/src/setup.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ function upgradePouchDbSchema(dbName, db, tx, pouchdbVersion) {
115115
}
116116

117117
function openDatabase(openDatabases, api, opts, resolve, reject) {
118-
const openReq = indexedDB.open(opts.name, createIdbVersion());
118+
const openReq = opts.versionChangedWhileOpen ?
119+
indexedDB.open(opts.name) :
120+
indexedDB.open(opts.name, createIdbVersion());
119121

120122
openReq.onupgradeneeded = function (e) {
121123
if (e.oldVersion > 0 && e.oldVersion < versionMultiplier) {
@@ -183,15 +185,23 @@ function openDatabase(openDatabases, api, opts, resolve, reject) {
183185
idb.close();
184186
};
185187

188+
// In IndexedDB you can only change the version, and thus the schema, when you are opening the database.
189+
// versionChangedWhileOpen means that something else outside of our control has likely updated the version.
190+
// One way this could happen is if you open multiple tabs, as the version number changes each time the database is opened.
191+
// If we suspect this we close the db and tag it, so that next time it's accessed it reopens the DB with the current version
192+
// as opposed to upping the version again
193+
// This avoids infinite loops of version updates if you have multiple tabs open
186194
idb.onversionchange = function () {
187195
console.log('Database was made stale, closing handle');
188-
delete openDatabases[opts.name];
196+
openDatabases[opts.name].versionChangedWhileOpen = true;
189197
idb.close();
190198
};
191199

192200
idb.onclose = function () {
193201
console.log('Database was made stale, closing handle');
194-
delete openDatabases[opts.name];
202+
if (opts.name in openDatabases) {
203+
openDatabases[opts.name].versionChangedWhileOpen = true;
204+
}
195205
};
196206

197207
let metadata = {id: META_LOCAL_STORE};
@@ -247,7 +257,10 @@ function openDatabase(openDatabases, api, opts, resolve, reject) {
247257
}
248258

249259
export default function (openDatabases, api, opts) {
250-
if (!openDatabases[opts.name]) {
260+
if (!openDatabases[opts.name] || openDatabases[opts.name].versionChangedWhileOpen) {
261+
opts.versionChangedWhileOpen = openDatabases[opts.name] &&
262+
openDatabases[opts.name].versionChangedWhileOpen;
263+
251264
openDatabases[opts.name] = new Promise(function (resolve, reject) {
252265
openDatabase(openDatabases, api, opts, resolve, reject);
253266
});

0 commit comments

Comments
 (0)