Skip to content

Commit fbf7222

Browse files
authored
Port BSON code updates from other sdks (#341)
1 parent 82f32ca commit fbf7222

File tree

10 files changed

+1226
-299
lines changed

10 files changed

+1226
-299
lines changed

common/api-review/firestore-lite.api.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,18 @@ export class BsonObjectId {
8888
export function bsonObjectId(value: string): BsonObjectId;
8989

9090
// @public
91-
export function bsonTimestamp(seconds: number, increment: number): BsonTimestampValue;
92-
93-
// @public
94-
export class BsonTimestampValue {
91+
export class BsonTimestamp {
9592
constructor(seconds: number, increment: number);
9693
// (undocumented)
9794
readonly increment: number;
98-
isEqual(other: BsonTimestampValue): boolean;
95+
isEqual(other: BsonTimestamp): boolean;
9996
// (undocumented)
10097
readonly seconds: number;
10198
}
10299

100+
// @public
101+
export function bsonTimestamp(seconds: number, increment: number): BsonTimestamp;
102+
103103
// @public
104104
export class Bytes {
105105
static fromBase64String(base64: string): Bytes;

common/api-review/firestore.api.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,18 @@ export class BsonObjectId {
8888
export function bsonObjectId(value: string): BsonObjectId;
8989

9090
// @public
91-
export function bsonTimestamp(seconds: number, increment: number): BsonTimestampValue;
92-
93-
// @public
94-
export class BsonTimestampValue {
91+
export class BsonTimestamp {
9592
constructor(seconds: number, increment: number);
9693
// (undocumented)
9794
readonly increment: number;
98-
isEqual(other: BsonTimestampValue): boolean;
95+
isEqual(other: BsonTimestamp): boolean;
9996
// (undocumented)
10097
readonly seconds: number;
10198
}
10299

100+
// @public
101+
export function bsonTimestamp(seconds: number, increment: number): BsonTimestamp;
102+
103103
// @public
104104
export class Bytes {
105105
static fromBase64String(base64: string): Bytes;

packages/firestore/src/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ export { BsonBinaryData } from './lite-api/bson_binary_data';
193193

194194
export { BsonObjectId } from './lite-api/bson_object_Id';
195195

196-
export { BsonTimestampValue } from './lite-api/bson_timestamp_value';
196+
export { BsonTimestamp } from './lite-api/bson_timestamp_value';
197197

198198
export { MinKey } from './lite-api/min_key';
199199

packages/firestore/src/index/firestore_index_value_writer.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ import {
2323
} from '../model/normalize';
2424
import {
2525
VECTOR_MAP_VECTORS_KEY,
26-
detectSpecialMapType,
26+
detectMapRepresentation,
2727
RESERVED_BSON_TIMESTAMP_KEY,
2828
RESERVED_REGEX_KEY,
2929
RESERVED_BSON_OBJECT_ID_KEY,
3030
RESERVED_BSON_BINARY_KEY,
31-
SpecialMapValueType,
31+
MapRepresentation,
3232
RESERVED_REGEX_PATTERN_KEY,
3333
RESERVED_REGEX_OPTIONS_KEY,
3434
RESERVED_INT32_KEY
@@ -42,6 +42,10 @@ import { DirectionalIndexByteEncoder } from './directional_index_byte_encoder';
4242
// Note: This file is copied from the backend. Code that is not used by
4343
// Firestore was removed. Code that has different behavior was modified.
4444

45+
// The client SDK only supports references to documents from the same database. We can skip the
46+
// first five segments.
47+
const DOCUMENT_NAME_OFFSET = 5;
48+
4549
const INDEX_TYPE_NULL = 5;
4650
const INDEX_TYPE_MIN_KEY = 7;
4751
const INDEX_TYPE_BOOLEAN = 10;
@@ -60,7 +64,7 @@ const INDEX_TYPE_ARRAY = 50;
6064
const INDEX_TYPE_VECTOR = 53;
6165
const INDEX_TYPE_MAP = 55;
6266
const INDEX_TYPE_REFERENCE_SEGMENT = 60;
63-
const INDEX_TYPE_MAX_VALUE = 999;
67+
const INDEX_TYPE_MAX_KEY = 999;
6468

6569
// A terminator that indicates that a truncatable value was not truncated.
6670
// This must be smaller than all other type labels.
@@ -137,24 +141,24 @@ export class FirestoreIndexValueWriter {
137141
encoder.writeNumber(geoPoint.latitude || 0);
138142
encoder.writeNumber(geoPoint.longitude || 0);
139143
} else if ('mapValue' in indexValue) {
140-
const type = detectSpecialMapType(indexValue);
141-
if (type === SpecialMapValueType.INTERNAL_MAX) {
144+
const type = detectMapRepresentation(indexValue);
145+
if (type === MapRepresentation.INTERNAL_MAX) {
142146
this.writeValueTypeLabel(encoder, Number.MAX_SAFE_INTEGER);
143-
} else if (type === SpecialMapValueType.VECTOR) {
147+
} else if (type === MapRepresentation.VECTOR) {
144148
this.writeIndexVector(indexValue.mapValue!, encoder);
145-
} else if (type === SpecialMapValueType.MAX_KEY) {
146-
this.writeValueTypeLabel(encoder, INDEX_TYPE_MAX_VALUE);
147-
} else if (type === SpecialMapValueType.MIN_KEY) {
149+
} else if (type === MapRepresentation.MAX_KEY) {
150+
this.writeValueTypeLabel(encoder, INDEX_TYPE_MAX_KEY);
151+
} else if (type === MapRepresentation.MIN_KEY) {
148152
this.writeValueTypeLabel(encoder, INDEX_TYPE_MIN_KEY);
149-
} else if (type === SpecialMapValueType.BSON_BINARY) {
153+
} else if (type === MapRepresentation.BSON_BINARY) {
150154
this.writeIndexBsonBinaryData(indexValue.mapValue!, encoder);
151-
} else if (type === SpecialMapValueType.REGEX) {
155+
} else if (type === MapRepresentation.REGEX) {
152156
this.writeIndexRegex(indexValue.mapValue!, encoder);
153-
} else if (type === SpecialMapValueType.BSON_TIMESTAMP) {
157+
} else if (type === MapRepresentation.BSON_TIMESTAMP) {
154158
this.writeIndexBsonTimestamp(indexValue.mapValue!, encoder);
155-
} else if (type === SpecialMapValueType.BSON_OBJECT_ID) {
159+
} else if (type === MapRepresentation.BSON_OBJECT_ID) {
156160
this.writeIndexBsonObjectId(indexValue.mapValue!, encoder);
157-
} else if (type === SpecialMapValueType.INT32) {
161+
} else if (type === MapRepresentation.INT32) {
158162
this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
159163
encoder.writeNumber(
160164
normalizeNumber(
@@ -237,7 +241,9 @@ export class FirestoreIndexValueWriter {
237241
const segments: string[] = referenceValue
238242
.split('/')
239243
.filter(segment => segment.length > 0);
240-
const path = DocumentKey.fromSegments(segments.slice(5)).path;
244+
const path = DocumentKey.fromSegments(
245+
segments.slice(DOCUMENT_NAME_OFFSET)
246+
).path;
241247
path.forEach(segment => {
242248
this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE_SEGMENT);
243249
this.writeUnlabeledIndexString(segment, encoder);

packages/firestore/src/model/values.ts

Lines changed: 62 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export const MIN_BSON_BINARY_VALUE: Value = {
160160
}
161161
};
162162

163-
export enum SpecialMapValueType {
163+
export enum MapRepresentation {
164164
REGEX = 'regexValue',
165165
BSON_OBJECT_ID = 'bsonObjectIdValue',
166166
INT32 = 'int32Value',
@@ -195,27 +195,27 @@ export function typeOrder(value: Value): TypeOrder {
195195
} else if ('arrayValue' in value) {
196196
return TypeOrder.ArrayValue;
197197
} else if ('mapValue' in value) {
198-
const valueType = detectSpecialMapType(value);
198+
const valueType = detectMapRepresentation(value);
199199
switch (valueType) {
200-
case SpecialMapValueType.SERVER_TIMESTAMP:
200+
case MapRepresentation.SERVER_TIMESTAMP:
201201
return TypeOrder.ServerTimestampValue;
202-
case SpecialMapValueType.INTERNAL_MAX:
202+
case MapRepresentation.INTERNAL_MAX:
203203
return TypeOrder.MaxValue;
204-
case SpecialMapValueType.VECTOR:
204+
case MapRepresentation.VECTOR:
205205
return TypeOrder.VectorValue;
206-
case SpecialMapValueType.REGEX:
206+
case MapRepresentation.REGEX:
207207
return TypeOrder.RegexValue;
208-
case SpecialMapValueType.BSON_OBJECT_ID:
208+
case MapRepresentation.BSON_OBJECT_ID:
209209
return TypeOrder.BsonObjectIdValue;
210-
case SpecialMapValueType.INT32:
210+
case MapRepresentation.INT32:
211211
return TypeOrder.NumberValue;
212-
case SpecialMapValueType.BSON_TIMESTAMP:
212+
case MapRepresentation.BSON_TIMESTAMP:
213213
return TypeOrder.BsonTimestampValue;
214-
case SpecialMapValueType.BSON_BINARY:
214+
case MapRepresentation.BSON_BINARY:
215215
return TypeOrder.BsonBinaryValue;
216-
case SpecialMapValueType.MIN_KEY:
216+
case MapRepresentation.MIN_KEY:
217217
return TypeOrder.MinKeyValue;
218-
case SpecialMapValueType.MAX_KEY:
218+
case MapRepresentation.MAX_KEY:
219219
return TypeOrder.MaxKeyValue;
220220
default:
221221
return TypeOrder.ObjectValue;
@@ -319,8 +319,8 @@ function blobEquals(left: Value, right: Value): boolean {
319319
export function numberEquals(left: Value, right: Value): boolean {
320320
if (
321321
('integerValue' in left && 'integerValue' in right) ||
322-
(detectSpecialMapType(left) === SpecialMapValueType.INT32 &&
323-
detectSpecialMapType(right) === SpecialMapValueType.INT32)
322+
(detectMapRepresentation(left) === MapRepresentation.INT32 &&
323+
detectMapRepresentation(right) === MapRepresentation.INT32)
324324
) {
325325
return extractNumber(left) === extractNumber(right);
326326
} else if ('doubleValue' in left && 'doubleValue' in right) {
@@ -427,7 +427,7 @@ export function valueCompare(left: Value, right: Value): number {
427427

428428
export function extractNumber(value: Value): number {
429429
let numberValue;
430-
if (detectSpecialMapType(value) === SpecialMapValueType.INT32) {
430+
if (detectMapRepresentation(value) === MapRepresentation.INT32) {
431431
numberValue = value.mapValue!.fields![RESERVED_INT32_KEY].integerValue!;
432432
} else {
433433
numberValue = value.integerValue || value.doubleValue;
@@ -686,10 +686,6 @@ function canonifyValue(value: Value): string {
686686
} else if ('arrayValue' in value) {
687687
return canonifyArray(value.arrayValue!);
688688
} else if ('mapValue' in value) {
689-
// BsonBinaryValue contains an array of bytes, and needs to extract `subtype` and `data` from it before canonifying.
690-
if (detectSpecialMapType(value) === SpecialMapValueType.BSON_BINARY) {
691-
return canonifyBsonBinaryData(value.mapValue!);
692-
}
693689
return canonifyMap(value.mapValue!);
694690
} else {
695691
return fail('Invalid value type: ' + JSON.stringify(value));
@@ -713,19 +709,6 @@ function canonifyReference(referenceValue: string): string {
713709
return DocumentKey.fromName(referenceValue).toString();
714710
}
715711

716-
function canonifyBsonBinaryData(mapValue: MapValue): string {
717-
const fields = mapValue!.fields?.[RESERVED_BSON_BINARY_KEY];
718-
const subtypeAndData = fields?.bytesValue;
719-
if (!subtypeAndData) {
720-
throw new Error('Received incorrect bytesValue for BsonBinaryData');
721-
}
722-
// Normalize the bytesValue to Uint8Array before extracting subtype and data.
723-
const bytes = normalizeByteString(subtypeAndData).toUint8Array();
724-
return `{__binary__:{subType:${bytes.at(0)},data:${canonifyByteString(
725-
bytes.slice(1)
726-
)}}}`;
727-
}
728-
729712
function canonifyMap(mapValue: MapValue): string {
730713
// Iteration order in JavaScript is not guaranteed. To ensure that we generate
731714
// matching canonical IDs for identical maps, we need to sort the keys.
@@ -886,35 +869,41 @@ export function isMapValue(
886869
return !!value && 'mapValue' in value;
887870
}
888871

889-
export function detectSpecialMapType(value: Value): SpecialMapValueType {
872+
export function detectMapRepresentation(value: Value): MapRepresentation {
890873
if (!value || !value.mapValue || !value.mapValue.fields) {
891-
return SpecialMapValueType.REGULAR_MAP; // Not a special map type
874+
return MapRepresentation.REGULAR_MAP; // Not a special map type
892875
}
893876

894877
const fields = value.mapValue.fields;
895878

896879
// Check for type-based mappings
897880
const type = fields[TYPE_KEY]?.stringValue;
898881
if (type) {
899-
const typeMap: Record<string, SpecialMapValueType> = {
900-
[RESERVED_VECTOR_KEY]: SpecialMapValueType.VECTOR,
901-
[RESERVED_MAX_KEY]: SpecialMapValueType.INTERNAL_MAX,
902-
[RESERVED_SERVER_TIMESTAMP_KEY]: SpecialMapValueType.SERVER_TIMESTAMP
882+
const typeMap: Record<string, MapRepresentation> = {
883+
[RESERVED_VECTOR_KEY]: MapRepresentation.VECTOR,
884+
[RESERVED_MAX_KEY]: MapRepresentation.INTERNAL_MAX,
885+
[RESERVED_SERVER_TIMESTAMP_KEY]: MapRepresentation.SERVER_TIMESTAMP
903886
};
904887
if (typeMap[type]) {
905888
return typeMap[type];
906889
}
907890
}
908891

892+
if (objectSize(fields) !== 1) {
893+
// All BSON types have 1 key in the map. To improve performance, we can
894+
// return early if the number of keys in the map is not 1.
895+
return MapRepresentation.REGULAR_MAP;
896+
}
897+
909898
// Check for BSON-related mappings
910-
const bsonMap: Record<string, SpecialMapValueType> = {
911-
[RESERVED_REGEX_KEY]: SpecialMapValueType.REGEX,
912-
[RESERVED_BSON_OBJECT_ID_KEY]: SpecialMapValueType.BSON_OBJECT_ID,
913-
[RESERVED_INT32_KEY]: SpecialMapValueType.INT32,
914-
[RESERVED_BSON_TIMESTAMP_KEY]: SpecialMapValueType.BSON_TIMESTAMP,
915-
[RESERVED_BSON_BINARY_KEY]: SpecialMapValueType.BSON_BINARY,
916-
[RESERVED_MIN_KEY]: SpecialMapValueType.MIN_KEY,
917-
[RESERVED_MAX_KEY]: SpecialMapValueType.MAX_KEY
899+
const bsonMap: Record<string, MapRepresentation> = {
900+
[RESERVED_REGEX_KEY]: MapRepresentation.REGEX,
901+
[RESERVED_BSON_OBJECT_ID_KEY]: MapRepresentation.BSON_OBJECT_ID,
902+
[RESERVED_INT32_KEY]: MapRepresentation.INT32,
903+
[RESERVED_BSON_TIMESTAMP_KEY]: MapRepresentation.BSON_TIMESTAMP,
904+
[RESERVED_BSON_BINARY_KEY]: MapRepresentation.BSON_BINARY,
905+
[RESERVED_MIN_KEY]: MapRepresentation.MIN_KEY,
906+
[RESERVED_MAX_KEY]: MapRepresentation.MAX_KEY
918907
};
919908

920909
for (const key in bsonMap) {
@@ -923,20 +912,20 @@ export function detectSpecialMapType(value: Value): SpecialMapValueType {
923912
}
924913
}
925914

926-
return SpecialMapValueType.REGULAR_MAP;
915+
return MapRepresentation.REGULAR_MAP;
927916
}
928917

929918
export function isBsonType(value: Value): boolean {
930919
const bsonTypes = new Set([
931-
SpecialMapValueType.REGEX,
932-
SpecialMapValueType.BSON_OBJECT_ID,
933-
SpecialMapValueType.INT32,
934-
SpecialMapValueType.BSON_TIMESTAMP,
935-
SpecialMapValueType.BSON_BINARY,
936-
SpecialMapValueType.MIN_KEY,
937-
SpecialMapValueType.MAX_KEY
920+
MapRepresentation.REGEX,
921+
MapRepresentation.BSON_OBJECT_ID,
922+
MapRepresentation.INT32,
923+
MapRepresentation.BSON_TIMESTAMP,
924+
MapRepresentation.BSON_BINARY,
925+
MapRepresentation.MIN_KEY,
926+
MapRepresentation.MAX_KEY
938927
]);
939-
return bsonTypes.has(detectSpecialMapType(value));
928+
return bsonTypes.has(detectMapRepresentation(value));
940929
}
941930

942931
/** Creates a deep copy of `source`. */
@@ -987,23 +976,23 @@ export function valuesGetLowerBound(value: Value): Value {
987976
} else if ('arrayValue' in value) {
988977
return { arrayValue: {} };
989978
} else if ('mapValue' in value) {
990-
const type = detectSpecialMapType(value);
991-
if (type === SpecialMapValueType.VECTOR) {
979+
const type = detectMapRepresentation(value);
980+
if (type === MapRepresentation.VECTOR) {
992981
return MIN_VECTOR_VALUE;
993-
} else if (type === SpecialMapValueType.BSON_OBJECT_ID) {
982+
} else if (type === MapRepresentation.BSON_OBJECT_ID) {
994983
return MIN_BSON_OBJECT_ID_VALUE;
995-
} else if (type === SpecialMapValueType.BSON_TIMESTAMP) {
984+
} else if (type === MapRepresentation.BSON_TIMESTAMP) {
996985
return MIN_BSON_TIMESTAMP_VALUE;
997-
} else if (type === SpecialMapValueType.BSON_BINARY) {
986+
} else if (type === MapRepresentation.BSON_BINARY) {
998987
return MIN_BSON_BINARY_VALUE;
999-
} else if (type === SpecialMapValueType.REGEX) {
988+
} else if (type === MapRepresentation.REGEX) {
1000989
return MIN_REGEX_VALUE;
1001-
} else if (type === SpecialMapValueType.INT32) {
990+
} else if (type === MapRepresentation.INT32) {
1002991
// int32Value is treated the same as integerValue and doubleValue
1003992
return { doubleValue: NaN };
1004-
} else if (type === SpecialMapValueType.MIN_KEY) {
993+
} else if (type === MapRepresentation.MIN_KEY) {
1005994
return MIN_KEY_VALUE;
1006-
} else if (type === SpecialMapValueType.MAX_KEY) {
995+
} else if (type === MapRepresentation.MAX_KEY) {
1007996
return MAX_KEY_VALUE;
1008997
}
1009998
return { mapValue: {} };
@@ -1033,23 +1022,23 @@ export function valuesGetUpperBound(value: Value): Value {
10331022
} else if ('arrayValue' in value) {
10341023
return MIN_VECTOR_VALUE;
10351024
} else if ('mapValue' in value) {
1036-
const type = detectSpecialMapType(value);
1037-
if (type === SpecialMapValueType.VECTOR) {
1025+
const type = detectMapRepresentation(value);
1026+
if (type === MapRepresentation.VECTOR) {
10381027
return { mapValue: {} };
1039-
} else if (type === SpecialMapValueType.BSON_OBJECT_ID) {
1028+
} else if (type === MapRepresentation.BSON_OBJECT_ID) {
10401029
return { geoPointValue: { latitude: -90, longitude: -180 } };
1041-
} else if (type === SpecialMapValueType.BSON_TIMESTAMP) {
1030+
} else if (type === MapRepresentation.BSON_TIMESTAMP) {
10421031
return { stringValue: '' };
1043-
} else if (type === SpecialMapValueType.BSON_BINARY) {
1032+
} else if (type === MapRepresentation.BSON_BINARY) {
10441033
return refValue(DatabaseId.empty(), DocumentKey.empty());
1045-
} else if (type === SpecialMapValueType.REGEX) {
1034+
} else if (type === MapRepresentation.REGEX) {
10461035
return { arrayValue: {} };
1047-
} else if (type === SpecialMapValueType.INT32) {
1036+
} else if (type === MapRepresentation.INT32) {
10481037
// int32Value is treated the same as integerValue and doubleValue
10491038
return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
1050-
} else if (type === SpecialMapValueType.MIN_KEY) {
1039+
} else if (type === MapRepresentation.MIN_KEY) {
10511040
return { booleanValue: false };
1052-
} else if (type === SpecialMapValueType.MAX_KEY) {
1041+
} else if (type === MapRepresentation.MAX_KEY) {
10531042
return INTERNAL_MAX_VALUE;
10541043
}
10551044
return MAX_KEY_VALUE;

0 commit comments

Comments
 (0)