Skip to content

Commit 7ee1955

Browse files
committed
Implement VectorValue type support.
1 parent 4b49630 commit 7ee1955

File tree

8 files changed

+322
-5
lines changed

8 files changed

+322
-5
lines changed

packages/firestore/src/lite-api/field_value_impl.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
NumericIncrementFieldValueImpl,
2424
ServerTimestampFieldValueImpl
2525
} from './user_data_reader';
26+
import { VectorValue } from './vector_value';
2627

2728
/**
2829
* Returns a sentinel for use with {@link @firebase/firestore/lite#(updateDoc:1)} or
@@ -97,3 +98,14 @@ export function arrayRemove(...elements: unknown[]): FieldValue {
9798
export function increment(n: number): FieldValue {
9899
return new NumericIncrementFieldValueImpl('increment', n);
99100
}
101+
102+
/**
103+
* Creates a new `VectorValue` constructed with a copy of the given array of numbers.
104+
*
105+
* @param values - Create a `VectorValue` instance with a copy of this array of numbers.
106+
*
107+
* @returns A new `VectorValue` constructed with a copy of the given array of numbers.
108+
*/
109+
export function vector(values?: number[]): VectorValue {
110+
return new VectorValue(values);
111+
}

packages/firestore/src/lite-api/user_data_reader.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { ParseContext } from '../api/parse_context';
2626
import { DatabaseId } from '../core/database_info';
2727
import { DocumentKey } from '../model/document_key';
2828
import { FieldMask } from '../model/field_mask';
29+
import { vectorValue } from '../model/map_type';
2930
import {
3031
FieldTransform,
3132
Mutation,
@@ -69,6 +70,7 @@ import {
6970
WithFieldValue
7071
} from './reference';
7172
import { Timestamp } from './timestamp';
73+
import { VectorValue } from './vector_value';
7274

7375
const RESERVED_FIELD_REGEX = /^__.*__$/;
7476

@@ -901,6 +903,9 @@ function parseScalarValue(
901903
value._key.path
902904
)
903905
};
906+
}
907+
if (value instanceof VectorValue) {
908+
return vectorValue(value);
904909
} else {
905910
throw context.createError(
906911
`Unsupported field value: ${valueDescription(value)}`
@@ -925,7 +930,8 @@ function looksLikeJsonObject(input: unknown): boolean {
925930
!(input instanceof GeoPoint) &&
926931
!(input instanceof Bytes) &&
927932
!(input instanceof DocumentReference) &&
928-
!(input instanceof FieldValue)
933+
!(input instanceof FieldValue) &&
934+
!(input instanceof VectorValue)
929935
);
930936
}
931937

packages/firestore/src/lite-api/user_data_writer.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { DocumentData } from '@firebase/firestore-types';
1919

2020
import { DatabaseId } from '../core/database_info';
2121
import { DocumentKey } from '../model/document_key';
22+
import { VECTOR_MAP_VECTORS_KEY } from '../model/map_type';
2223
import {
2324
normalizeByteString,
2425
normalizeNumber,
@@ -48,6 +49,7 @@ import { forEach } from '../util/obj';
4849

4950
import { GeoPoint } from './geo_point';
5051
import { Timestamp } from './timestamp';
52+
import { VectorValue } from './vector_value';
5153

5254
export type ServerTimestampBehavior = 'estimate' | 'previous' | 'none';
5355

@@ -85,6 +87,8 @@ export abstract class AbstractUserDataWriter {
8587
return this.convertArray(value.arrayValue!, serverTimestampBehavior);
8688
case TypeOrder.ObjectValue:
8789
return this.convertObject(value.mapValue!, serverTimestampBehavior);
90+
case TypeOrder.VectorValue:
91+
return this.convertVectorValue(value.mapValue!);
8892
default:
8993
throw fail('Invalid value type: ' + JSON.stringify(value));
9094
}
@@ -111,6 +115,19 @@ export abstract class AbstractUserDataWriter {
111115
return result;
112116
}
113117

118+
/**
119+
* @internal
120+
*/
121+
convertVectorValue(mapValue: ProtoMapValue): VectorValue {
122+
const values = mapValue.fields?.[
123+
VECTOR_MAP_VECTORS_KEY
124+
].arrayValue?.values?.map(value => {
125+
return normalizeNumber(value.doubleValue);
126+
});
127+
128+
return new VectorValue(values);
129+
}
130+
114131
private convertGeoPoint(value: ProtoLatLng): GeoPoint {
115132
return new GeoPoint(
116133
normalizeNumber(value.latitude),
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { isPrimitiveArrayEqual } from '../util/array';
2+
3+
/**
4+
* Represent a vector type in Firestore documents.
5+
* Create an instance with {@link FieldValue.vector}.
6+
*
7+
* @class VectorValue
8+
*/
9+
export class VectorValue {
10+
private readonly _values: number[];
11+
12+
/**
13+
* @private
14+
* @internal
15+
*/
16+
constructor(values: number[] | undefined) {
17+
// Making a copy of the parameter.
18+
this._values = (values || []).map(n => n);
19+
}
20+
21+
/**
22+
* Returns a copy of the raw number array form of the vector.
23+
*/
24+
toArray(): number[] {
25+
return this._values.map(n => n);
26+
}
27+
28+
/**
29+
* Returns `true` if the two VectorValue has the same raw number arrays, returns `false` otherwise.
30+
*/
31+
isEqual(other: VectorValue): boolean {
32+
return isPrimitiveArrayEqual(this._values, other._values);
33+
}
34+
}

packages/firestore/src/model/type_order.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const enum TypeOrder {
3535
RefValue = 7,
3636
GeoPointValue = 8,
3737
ArrayValue = 9,
38-
ObjectValue = 10,
38+
VectorValue = 10,
39+
ObjectValue = 11,
3940
MaxValue = 9007199254740991 // Number.MAX_SAFE_INTEGER
4041
}

packages/firestore/src/model/values.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { forEach, objectSize } from '../util/obj';
2929
import { isNegativeZero } from '../util/types';
3030

3131
import { DocumentKey } from './document_key';
32+
import { isVectorValue, VECTOR_MAP_VECTORS_KEY } from './map_type';
3233
import {
3334
normalizeByteString,
3435
normalizeNumber,
@@ -79,6 +80,8 @@ export function typeOrder(value: Value): TypeOrder {
7980
return TypeOrder.ServerTimestampValue;
8081
} else if (isMaxValue(value)) {
8182
return TypeOrder.MaxValue;
83+
} else if (isVectorValue(value)) {
84+
return TypeOrder.VectorValue;
8285
}
8386
return TypeOrder.ObjectValue;
8487
} else {
@@ -123,6 +126,7 @@ export function valueEquals(left: Value, right: Value): boolean {
123126
right.arrayValue!.values || [],
124127
valueEquals
125128
);
129+
case TypeOrder.VectorValue:
126130
case TypeOrder.ObjectValue:
127131
return objectEquals(left, right);
128132
case TypeOrder.MaxValue:
@@ -252,6 +256,8 @@ export function valueCompare(left: Value, right: Value): number {
252256
return compareGeoPoints(left.geoPointValue!, right.geoPointValue!);
253257
case TypeOrder.ArrayValue:
254258
return compareArrays(left.arrayValue!, right.arrayValue!);
259+
case TypeOrder.VectorValue:
260+
return compareVectors(left.mapValue!, right.mapValue!);
255261
case TypeOrder.ObjectValue:
256262
return compareMaps(left.mapValue!, right.mapValue!);
257263
default:
@@ -349,6 +355,25 @@ function compareArrays(left: ArrayValue, right: ArrayValue): number {
349355
return primitiveComparator(leftArray.length, rightArray.length);
350356
}
351357

358+
function compareVectors(left: MapValue, right: MapValue): number {
359+
const leftMap = left.fields || {};
360+
const rightMap = right.fields || {};
361+
362+
// The vector is a map, but only vector value is compared.
363+
const leftArrayValue = leftMap[VECTOR_MAP_VECTORS_KEY]?.arrayValue;
364+
const rightArrayValue = rightMap[VECTOR_MAP_VECTORS_KEY]?.arrayValue;
365+
366+
const lengthCompare = primitiveComparator(
367+
leftArrayValue?.values?.length || 0,
368+
rightArrayValue?.values?.length || 0
369+
);
370+
if (lengthCompare !== 0) {
371+
return lengthCompare;
372+
}
373+
374+
return compareArrays(leftArrayValue!, rightArrayValue!);
375+
}
376+
352377
function compareMaps(left: MapValue, right: MapValue): number {
353378
if (left === MAX_VALUE.mapValue && right === MAX_VALUE.mapValue) {
354379
return 0;
@@ -504,6 +529,7 @@ export function estimateByteSize(value: Value): number {
504529
return 16;
505530
case TypeOrder.ArrayValue:
506531
return estimateArrayByteSize(value.arrayValue!);
532+
case TypeOrder.VectorValue:
507533
case TypeOrder.ObjectValue:
508534
return estimateMapByteSize(value.mapValue!);
509535
default:

packages/firestore/src/util/array.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function includes<T>(array: T[], value: T): boolean {
2828
}
2929

3030
/**
31-
* Returns true iff the array contains any value mathching the predicate
31+
* Returns true iff the array contains any value matching the predicate
3232
*/
3333
export function some<T>(array: T[], predicate: (t: T) => boolean): boolean {
3434
for (let i = 0; i < array.length; i++) {
@@ -111,3 +111,55 @@ export function diffArrays<T>(
111111
onRemove(before[b++]);
112112
}
113113
}
114+
115+
/**
116+
* Verifies equality for an array of objects using the `isEqual` interface.
117+
*
118+
* @private
119+
* @internal
120+
* @param left Array of objects supporting `isEqual`.
121+
* @param right Array of objects supporting `isEqual`.
122+
* @return True if arrays are equal.
123+
*/
124+
export function isArrayEqual<T extends { isEqual: (t: T) => boolean }>(
125+
left: T[],
126+
right: T[]
127+
): boolean {
128+
if (left.length !== right.length) {
129+
return false;
130+
}
131+
132+
for (let i = 0; i < left.length; ++i) {
133+
if (!left[i].isEqual(right[i])) {
134+
return false;
135+
}
136+
}
137+
138+
return true;
139+
}
140+
141+
/**
142+
* Verifies equality for an array of primitives.
143+
*
144+
* @private
145+
* @internal
146+
* @param left Array of primitives.
147+
* @param right Array of primitives.
148+
* @return True if arrays are equal.
149+
*/
150+
export function isPrimitiveArrayEqual<T extends number | string>(
151+
left: T[],
152+
right: T[]
153+
): boolean {
154+
if (left.length !== right.length) {
155+
return false;
156+
}
157+
158+
for (let i = 0; i < left.length; ++i) {
159+
if (left[i] !== right[i]) {
160+
return false;
161+
}
162+
}
163+
164+
return true;
165+
}

0 commit comments

Comments
 (0)