Skip to content

Commit 74d0730

Browse files
committed
chore: reduce caches
1 parent 4a7f7d4 commit 74d0730

File tree

5 files changed

+49
-45
lines changed

5 files changed

+49
-45
lines changed

package-lock.json

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
},
2727
"dependencies": {
2828
"@mongodb-js/saslprep": "^1.1.5",
29-
"bson": "github:mongodb/js-bson#NODE-6059-cleanup",
29+
"bson": "github:mongodb/js-bson#main",
3030
"mongodb-connection-string-url": "^3.0.0"
3131
},
3232
"peerDependencies": {

src/bson.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,8 @@ export function parseToElementsToArray(bytes: Uint8Array, offset?: number): BSON
3333
const res = BSON.onDemand.parseToElements(bytes, offset);
3434
return Array.isArray(res) ? res : [...res];
3535
}
36-
37-
// eslint-disable-next-line @typescript-eslint/unbound-method
3836
export const getInt32LE = BSON.onDemand.NumberUtils.getInt32LE;
39-
// eslint-disable-next-line @typescript-eslint/unbound-method
4037
export const getFloat64LE = BSON.onDemand.NumberUtils.getFloat64LE;
41-
// eslint-disable-next-line @typescript-eslint/unbound-method
4238
export const getBigInt64LE = BSON.onDemand.NumberUtils.getBigInt64LE;
4339
export const toUTF8 = BSON.onDemand.ByteUtils.toUTF8;
4440

src/cmap/wire_protocol/on_demand/document.ts

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,20 @@ export type JSTypeOf = {
3939
[BSONType.array]: OnDemandDocument;
4040
};
4141

42+
/** @internal */
43+
type CachedBSONElement = { element: BSONElement; value: any | undefined };
44+
4245
/** @internal */
4346
export class OnDemandDocument {
44-
/** Caches the existence of a property */
45-
private readonly existenceOf: Record<string, boolean> = Object.create(null);
46-
/** Caches a look up of name to element */
47-
private readonly elementOf: Record<string, BSONElement> = Object.create(null);
48-
/** Caches the revived javascript value */
49-
private readonly valueOf: Record<string, any> = Object.create(null);
47+
/**
48+
* Maps JS strings to elements and jsValues for speeding up subsequent lookups.
49+
* - If `false` then name does not exist in the BSON document
50+
* - If `CachedBSONElement` instance name exists
51+
* - If `cache[name].value == null` jsValue has not yet been parsed
52+
* - Null/Undefined values do not get cached because they are zero-length values.
53+
*/
54+
private readonly cache: Record<string, CachedBSONElement | false | undefined> =
55+
Object.create(null);
5056
/** Caches the index of elements that have been named */
5157
private readonly indexFound: Record<number, boolean> = Object.create(null);
5258

@@ -90,29 +96,27 @@ export class OnDemandDocument {
9096
* @param name - a basic latin string name of a BSON element
9197
* @returns
9298
*/
93-
private getElement(name: string): BSONElement | null {
94-
if (this.existenceOf[name] === false) return null;
99+
private getElement(name: string): CachedBSONElement | null {
100+
const cachedElement = this.cache[name];
101+
if (cachedElement === false) return null;
95102

96-
if (this.elementOf[name] != null) {
97-
return this.elementOf[name];
103+
if (cachedElement != null) {
104+
return cachedElement;
98105
}
99106

100107
for (let index = 0; index < this.elements.length; index++) {
101108
const element = this.elements[index];
102109

103-
if (
104-
// skip this element if it has already been associated with a name
105-
!this.indexFound[index] &&
106-
this.isElementName(name, element)
107-
) {
108-
this.elementOf[name] = element;
110+
// skip this element if it has already been associated with a name
111+
if (!this.indexFound[index] && this.isElementName(name, element)) {
112+
const cachedElement = { element, value: undefined };
113+
this.cache[name] = cachedElement;
109114
this.indexFound[index] = true;
110-
this.existenceOf[name] = true;
111-
return this.elementOf[name];
115+
return cachedElement;
112116
}
113117
}
114118

115-
this.existenceOf[name] = false;
119+
this.cache[name] = false;
116120
return null;
117121
}
118122

@@ -202,7 +206,10 @@ export class OnDemandDocument {
202206
* @param name - element name
203207
*/
204208
public has(name: string): boolean {
205-
return (this.existenceOf[name] ??= this.getElement(name) != null);
209+
const cachedElement = this.cache[name];
210+
if (cachedElement === false) return false;
211+
if (cachedElement != null) return true;
212+
return this.getElement(name) != null;
206213
}
207214

208215
/**
@@ -236,8 +243,8 @@ export class OnDemandDocument {
236243
}
237244
}
238245

239-
if (!(name in this.valueOf)) {
240-
const value = this.toJSValue(element, as);
246+
if (element.value == null) {
247+
const value = this.toJSValue(element.element, as);
241248
if (value == null) {
242249
if (required === true) {
243250
throw new BSONError(`BSON element "${name}" is missing`);
@@ -246,10 +253,10 @@ export class OnDemandDocument {
246253
}
247254
}
248255
// It is important to never store null
249-
this.valueOf[name] = value;
256+
element.value = value;
250257
}
251258

252-
return this.valueOf[name];
259+
return element.value;
253260
}
254261

255262
/**
@@ -306,9 +313,9 @@ export class OnDemandDocument {
306313
}
307314
let counter = 0;
308315
for (const element of this.elements) {
309-
const item = this.toJSValue<T>(element, as);
310-
this.valueOf[counter] = item;
311-
yield item;
316+
const value = this.toJSValue<T>(element, as);
317+
this.cache[counter] = { element, value };
318+
yield value;
312319
counter += 1;
313320
}
314321
}

test/unit/cmap/wire_protocol/on_demand/document.test.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ describe('class OnDemandDocument', () => {
1717
const doc = new OnDemandDocument(emptyDocument, 0, false);
1818
expect(doc.has('ok')).to.be.false;
1919
expect(doc.has('$clusterTime')).to.be.false;
20-
expect(doc).to.have.nested.property('existenceOf.ok', false);
21-
expect(doc).to.have.nested.property('existenceOf.$clusterTime', false);
20+
expect(doc).to.have.nested.property('cache.ok', false);
21+
expect(doc).to.have.nested.property('cache.$clusterTime', false);
2222
});
2323
});
2424

@@ -27,14 +27,14 @@ describe('class OnDemandDocument', () => {
2727
const emptyDocument = BSON.serialize({ ok: 1 });
2828
const doc = new OnDemandDocument(emptyDocument, 0, false);
2929
expect(doc.has('ok')).to.be.true;
30-
expect(doc).to.have.nested.property('existenceOf.ok', true);
30+
expect(doc).to.have.nested.property('cache.ok').that.is.an('object');
3131
});
3232

3333
it('sets exists cache to false for any other key', () => {
3434
const emptyDocument = BSON.serialize({ ok: 1 });
3535
const doc = new OnDemandDocument(emptyDocument, 0, false);
3636
expect(doc.has('$clusterTime')).to.be.false;
37-
expect(doc).to.have.nested.property('existenceOf.$clusterTime', false);
37+
expect(doc).to.have.nested.property('cache.$clusterTime', false);
3838
});
3939
});
4040

@@ -43,7 +43,7 @@ describe('class OnDemandDocument', () => {
4343
const emptyDocument = BSON.serialize({ ok: 0, code: 2 });
4444
const doc = new OnDemandDocument(emptyDocument, 0, false);
4545
expect(doc.has('code')).to.be.true;
46-
expect(doc).to.have.nested.property('existenceOf.code', true);
46+
expect(doc).to.have.nested.property('cache.code').that.is.an('object');
4747
expect(doc).to.not.have.nested.property('indexFound.0');
4848
expect(doc).to.have.nested.property('indexFound.1', true);
4949
});
@@ -116,7 +116,7 @@ describe('class OnDemandDocument', () => {
116116

117117
it('throws if required is set to true and element name does not exist', () => {
118118
expect(() => document.get('blah!', BSONType.bool, true)).to.throw(BSONError);
119-
expect(document).to.have.nested.property('existenceOf.blah!', false);
119+
expect(document).to.have.nested.property('cache.blah!', false);
120120
});
121121

122122
it('throws if requested type is unsupported', () => {
@@ -127,8 +127,10 @@ describe('class OnDemandDocument', () => {
127127
});
128128

129129
it('caches the value', () => {
130+
document.has('int');
131+
expect(document).to.have.nested.property('cache.int.value', undefined);
130132
document.get('int', BSONType.int);
131-
expect(document).to.have.nested.property('valueOf.int', 1);
133+
expect(document).to.have.nested.property('cache.int.value', 1);
132134
});
133135

134136
it('supports returning null for null and undefined bson elements', () => {
@@ -284,12 +286,12 @@ describe('class OnDemandDocument', () => {
284286
expect(didRun).to.be.true;
285287
});
286288

287-
it('caches the results in valueOf', () => {
289+
it('caches the results of array', () => {
288290
const generator = array.valuesAs(BSONType.int);
289291
generator.next();
290292
generator.next();
291-
expect(array).to.have.nested.property('valueOf.0', 1);
292-
expect(array).to.have.nested.property('valueOf.1', 1);
293+
expect(array).to.have.nested.property('cache.0.value', 1);
294+
expect(array).to.have.nested.property('cache.1.value', 1);
293295
});
294296
});
295297
});

0 commit comments

Comments
 (0)