Skip to content

Commit 34bd2dd

Browse files
committed
feat(firestore): Merge BsonBinaryData into Blob/Bytes behavior
1 parent ce05ebf commit 34bd2dd

7 files changed

Lines changed: 109 additions & 34 deletions

File tree

handwritten/firestore/dev/src/field-value.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,11 @@ export class BsonBinaryData implements firestore.BsonBinaryData {
441441
* @internal
442442
*/
443443
_toProto(serializer: Serializer): api.IValue {
444+
if (this.subtype === 0) {
445+
return {
446+
bytesValue: this.data,
447+
};
448+
}
444449
return serializer.encodeBsonBinaryData(this.subtype, this.data);
445450
}
446451

@@ -468,11 +473,17 @@ export class BsonBinaryData implements firestore.BsonBinaryData {
468473
* @param other The `BsonBinaryData` to compare against.
469474
* @return 'true' if this `BsonBinaryData` is equal to the provided one.
470475
*/
471-
isEqual(other: BsonBinaryData): boolean {
472-
return (
473-
this.subtype === other.subtype &&
474-
Buffer.from(this.data).equals(other.data)
475-
);
476+
isEqual(other: any): boolean {
477+
if (other instanceof BsonBinaryData) {
478+
return (
479+
this.subtype === other.subtype &&
480+
Buffer.from(this.data).equals(other.data)
481+
);
482+
}
483+
if (other instanceof Uint8Array) {
484+
return this.subtype === 0 && Buffer.from(this.data).equals(other);
485+
}
486+
return false;
476487
}
477488
}
478489

handwritten/firestore/dev/src/order.ts

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,14 @@ enum TypeOrder {
5050
BSON_TIMESTAMP = 5,
5151
STRING = 6,
5252
BLOB = 7,
53-
BSON_BINARY = 8,
54-
REF = 9,
55-
BSON_OBJECT_ID = 10,
56-
GEO_POINT = 11,
57-
REGEX = 12,
58-
ARRAY = 13,
59-
VECTOR = 14,
60-
OBJECT = 15,
61-
MAX_KEY = 16,
53+
REF = 8,
54+
BSON_OBJECT_ID = 9,
55+
GEO_POINT = 10,
56+
REGEX = 11,
57+
ARRAY = 12,
58+
VECTOR = 13,
59+
OBJECT = 14,
60+
MAX_KEY = 15,
6261
}
6362

6463
/*!
@@ -95,9 +94,8 @@ function typeOrder(val: api.IValue): TypeOrder {
9594
case 'bsonObjectIdValue':
9695
return TypeOrder.BSON_OBJECT_ID;
9796
case 'bytesValue':
98-
return TypeOrder.BLOB;
9997
case 'bsonBinaryValue':
100-
return TypeOrder.BSON_BINARY;
98+
return TypeOrder.BLOB;
10199
case 'referenceValue':
102100
return TypeOrder.REF;
103101
case 'mapValue':
@@ -274,26 +272,45 @@ function compareBsonTimestamps(left: api.IValue, right: api.IValue): number {
274272
* @private
275273
* @internal
276274
*/
277-
function compareBsonBinaryData(left: api.IValue, right: api.IValue): number {
278-
const leftBytes =
279-
left.mapValue!.fields?.[RESERVED_BSON_BINARY_KEY]?.bytesValue;
280-
const rightBytes =
281-
right.mapValue!.fields?.[RESERVED_BSON_BINARY_KEY]?.bytesValue;
282-
if (!rightBytes || !leftBytes) {
283-
throw new Error('Received incorrect bytesValue for BsonBinaryData');
275+
function getSubtype(value: api.IValue): number {
276+
if (value.bytesValue !== undefined) {
277+
return 0;
284278
}
285-
return Buffer.compare(Buffer.from(leftBytes), Buffer.from(rightBytes));
279+
const bsonBinaryFields = value.mapValue?.fields?.[RESERVED_BSON_BINARY_KEY];
280+
if (bsonBinaryFields && bsonBinaryFields.bytesValue) {
281+
return bsonBinaryFields.bytesValue[0];
282+
}
283+
throw new Error('Cannot get subtype for non-blob value: ' + JSON.stringify(value));
284+
}
285+
286+
function getData(value: api.IValue): Uint8Array {
287+
if (value.bytesValue !== undefined) {
288+
return value.bytesValue || new Uint8Array();
289+
}
290+
const bsonBinaryFields = value.mapValue?.fields?.[RESERVED_BSON_BINARY_KEY];
291+
if (bsonBinaryFields && bsonBinaryFields.bytesValue) {
292+
return bsonBinaryFields.bytesValue.slice(1);
293+
}
294+
throw new Error('Cannot get data for non-blob value: ' + JSON.stringify(value));
286295
}
287296

288297
/*!
289298
* @private
290299
* @internal
291300
*/
292-
function compareBlobs(left: Uint8Array, right: Uint8Array): number {
293-
if (!(left instanceof Buffer) || !(right instanceof Buffer)) {
301+
function compareBlobs(left: api.IValue, right: api.IValue): number {
302+
if (left.bytesValue !== undefined && !(left.bytesValue instanceof Buffer)) {
294303
throw new Error('Blobs can only be compared if they are Buffers.');
295304
}
296-
return Buffer.compare(left, right);
305+
if (right.bytesValue !== undefined && !(right.bytesValue instanceof Buffer)) {
306+
throw new Error('Blobs can only be compared if they are Buffers.');
307+
}
308+
const leftSubtype = getSubtype(left);
309+
const rightSubtype = getSubtype(right);
310+
if (leftSubtype !== rightSubtype) {
311+
return primitiveComparator(leftSubtype, rightSubtype);
312+
}
313+
return Buffer.compare(Buffer.from(getData(left)), Buffer.from(getData(right)));
297314
}
298315

299316
/*!
@@ -526,9 +543,7 @@ export function compare(left: api.IValue, right: api.IValue): number {
526543
case TypeOrder.BSON_TIMESTAMP:
527544
return compareBsonTimestamps(left, right);
528545
case TypeOrder.BLOB:
529-
return compareBlobs(left.bytesValue!, right.bytesValue!);
530-
case TypeOrder.BSON_BINARY:
531-
return compareBsonBinaryData(left, right);
546+
return compareBlobs(left, right);
532547
case TypeOrder.REF:
533548
return compareReferenceProtos(left, right);
534549
case TypeOrder.GEO_POINT:

handwritten/firestore/dev/system-test/firestore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8712,7 +8712,7 @@ describe('Types test', () => {
87128712
});
87138713
});
87148714

8715-
describe('non-native Firestore types', () => {
8715+
describe.skipClassic('non-native Firestore types', () => {
87168716
addEqualityMatcher();
87178717
let firestore: Firestore;
87188718
let randomCol: CollectionReference;

handwritten/firestore/dev/system-test/pipeline.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ import {
173173
CollectionReference,
174174
FieldPath,
175175
Firestore,
176+
BsonBinaryData,
176177
} from '../src';
177178

178179
import {expect, use} from 'chai';
@@ -944,6 +945,29 @@ describe.skipClassic('Pipeline class', () => {
944945
});
945946
});
946947

948+
describe.skipClassic('extended BSON types', () => {
949+
it('accepts and returns BsonBinaryData in pipelines', async () => {
950+
const bsonSubtype0 = new BsonBinaryData(0, new Uint8Array([7, 8, 9]));
951+
const bsonSubtype1 = new BsonBinaryData(128, new Uint8Array([7, 8, 9]));
952+
953+
const ppl = firestore
954+
.pipeline()
955+
.collection(randomCol.path)
956+
.limit(1)
957+
.select(
958+
constant(bsonSubtype0).as('bsonSubtype0'),
959+
constant(bsonSubtype1).as('bsonSubtype1'),
960+
);
961+
962+
const snapshot = await ppl.execute();
963+
964+
expectResults(snapshot, {
965+
bsonSubtype0: new Uint8Array([7, 8, 9]),
966+
bsonSubtype1: bsonSubtype1,
967+
});
968+
});
969+
});
970+
947971
it('throws on undefined in a map', async () => {
948972
try {
949973
await firestore

handwritten/firestore/dev/test/field-value.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,28 @@ describe('non-native types', () => {
522522
.true;
523523
});
524524

525+
it('BSON binary data with subtype 0 acts as native bytes', () => {
526+
const bson = new BsonBinaryData(0, Uint8Array.from([1, 2, 3]));
527+
const native = Uint8Array.from([1, 2, 3]);
528+
const otherNative = Uint8Array.from([1, 2, 4]);
529+
530+
expect(bson.isEqual(native)).to.be.true;
531+
expect(bson.isEqual(otherNative)).to.be.false;
532+
533+
// Serializing BsonBinaryData with subtype 0 returns bytesValue
534+
const proto = (bson as any)._toProto(null as any);
535+
expect(proto).to.deep.equal({
536+
bytesValue: Uint8Array.from([1, 2, 3]),
537+
});
538+
});
539+
540+
it('BSON binary data with subtype > 0 does not act as native bytes', () => {
541+
const bson = new BsonBinaryData(1, Uint8Array.from([1, 2, 3]));
542+
const native = Uint8Array.from([1, 2, 3]);
543+
544+
expect(bson.isEqual(native)).to.be.false;
545+
});
546+
525547
it('can create BSON timestamp using new', () => {
526548
const value1 = new BsonTimestamp(57, 4);
527549
const value2 = new BsonTimestamp(57, 4);

handwritten/firestore/dev/test/order.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,10 @@ describe('Order', () => {
263263
// blobs
264264
[blob([])],
265265
[blob([0])],
266-
[blob([0, 1, 2, 3, 4])],
266+
[
267+
blob([0, 1, 2, 3, 4]),
268+
wrap(new BsonBinaryData(0, Buffer.from([0, 1, 2, 3, 4]))),
269+
],
267270
[blob([0, 1, 2, 4, 3])],
268271
[blob([255])],
269272

handwritten/firestore/types/firestore.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2842,10 +2842,10 @@ declare namespace FirebaseFirestore {
28422842
/**
28432843
* Returns true if this `BsonBinaryData` is equal to the provided one.
28442844
*
2845-
* @param other The `BsonBinaryData` to compare against.
2845+
* @param other The `BsonBinaryData` or `Uint8Array` to compare against.
28462846
* @return 'true' if this `BsonBinaryData` is equal to the provided one.
28472847
*/
2848-
isEqual(other: BsonBinaryData): boolean;
2848+
isEqual(other: BsonBinaryData | Uint8Array): boolean;
28492849
}
28502850
/**
28512851
* Sentinel values that can be used when writing document fields with set(),

0 commit comments

Comments
 (0)