Skip to content

Commit e6b8525

Browse files
Vector Type (#8215)
Implement VectorValue type support.
1 parent 1601572 commit e6b8525

25 files changed

+792
-572
lines changed

.changeset/nice-eyes-tan.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@firebase/firestore": minor
3+
"firebase": minor
4+
---
5+
6+
Add support for reading and writing Firestore vectors.

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

+10
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,16 @@ export function updateDoc<AppModelType, DbModelType extends DocumentData>(refere
460460
// @public
461461
export function updateDoc<AppModelType, DbModelType extends DocumentData>(reference: DocumentReference<AppModelType, DbModelType>, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise<void>;
462462

463+
// @public
464+
export function vector(values?: number[]): VectorValue;
465+
466+
// @public
467+
export class VectorValue {
468+
/* Excluded from this release type: __constructor */
469+
isEqual(other: VectorValue): boolean;
470+
toArray(): number[];
471+
}
472+
463473
// @public
464474
export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryFieldFilterConstraint;
465475

common/api-review/firestore.api.md

+10
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,16 @@ export function updateDoc<AppModelType, DbModelType extends DocumentData>(refere
745745
// @public
746746
export function updateDoc<AppModelType, DbModelType extends DocumentData>(reference: DocumentReference<AppModelType, DbModelType>, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise<void>;
747747

748+
// @public
749+
export function vector(values?: number[]): VectorValue;
750+
751+
// @public
752+
export class VectorValue {
753+
/* Excluded from this release type: __constructor */
754+
isEqual(other: VectorValue): boolean;
755+
toArray(): number[];
756+
}
757+
748758
// @public
749759
export function waitForPendingWrites(firestore: Firestore): Promise<void>;
750760

docs-devsite/_toc.yaml

-555
This file was deleted.

docs-devsite/firestore_.md

+27
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ https://github.com/firebase/firebase-js-sdk
124124
| [endBefore(snapshot)](./firestore_.md#endbefore_9a4477f) | Creates a [QueryEndAtConstraint](./firestore_.queryendatconstraint.md#queryendatconstraint_class) that modifies the result set to end before the provided document (exclusive). The end position is relative to the order of the query. The document must contain all of the fields provided in the orderBy of the query. |
125125
| [startAfter(snapshot)](./firestore_.md#startafter_9a4477f) | Creates a [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) that modifies the result set to start after the provided document (exclusive). The starting position is relative to the order of the query. The document must contain all of the fields provided in the orderBy of the query. |
126126
| [startAt(snapshot)](./firestore_.md#startat_9a4477f) | Creates a [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) that modifies the result set to start at the provided document (inclusive). The starting position is relative to the order of the query. The document must contain all of the fields provided in the <code>orderBy</code> of this query. |
127+
| <b>function(values, ...)</b> |
128+
| [vector(values)](./firestore_.md#vector_0dbdaf2) | Creates a new <code>VectorValue</code> constructed with a copy of the given array of numbers. |
127129

128130
## Classes
129131

@@ -155,6 +157,7 @@ https://github.com/firebase/firebase-js-sdk
155157
| [SnapshotMetadata](./firestore_.snapshotmetadata.md#snapshotmetadata_class) | Metadata about a snapshot, describing the state of the snapshot. |
156158
| [Timestamp](./firestore_.timestamp.md#timestamp_class) | A <code>Timestamp</code> represents a point in time independent of any time zone or calendar, represented as seconds and fractions of seconds at nanosecond resolution in UTC Epoch time.<!-- -->It is encoded using the Proleptic Gregorian Calendar which extends the Gregorian calendar backwards to year one. It is encoded assuming all minutes are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.<!-- -->For examples and further specifications, refer to the [Timestamp definition](https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto)<!-- -->. |
157159
| [Transaction](./firestore_.transaction.md#transaction_class) | A reference to a transaction.<!-- -->The <code>Transaction</code> object passed to a transaction's <code>updateFunction</code> provides the methods to read and write data within the transaction context. See [runTransaction()](./firestore_.md#runtransaction_6f03ec4)<!-- -->. |
160+
| [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | Represents a vector type in Firestore documents. Create an instance with . VectorValue |
158161
| [WriteBatch](./firestore_.writebatch.md#writebatch_class) | A write batch, used to perform multiple writes as a single atomic unit.<!-- -->A <code>WriteBatch</code> object can be acquired by calling [writeBatch()](./firestore_.md#writebatch_231a8e0)<!-- -->. It provides methods for adding writes to the write batch. None of the writes will be committed (or visible locally) until [WriteBatch.commit()](./firestore_.writebatch.md#writebatchcommit) is called. |
159162

160163
## Interfaces
@@ -2452,6 +2455,30 @@ export declare function startAt<AppModelType, DbModelType extends DocumentData>(
24522455

24532456
A [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) to pass to `query()`<!-- -->.
24542457

2458+
## function(values, ...)
2459+
2460+
### vector(values) {:#vector_0dbdaf2}
2461+
2462+
Creates a new `VectorValue` constructed with a copy of the given array of numbers.
2463+
2464+
<b>Signature:</b>
2465+
2466+
```typescript
2467+
export declare function vector(values?: number[]): VectorValue;
2468+
```
2469+
2470+
#### Parameters
2471+
2472+
| Parameter | Type | Description |
2473+
| --- | --- | --- |
2474+
| values | number\[\] | Create a <code>VectorValue</code> instance with a copy of this array of numbers. |
2475+
2476+
<b>Returns:</b>
2477+
2478+
[VectorValue](./firestore_.vectorvalue.md#vectorvalue_class)
2479+
2480+
A new `VectorValue` constructed with a copy of the given array of numbers.
2481+
24552482
## CACHE\_SIZE\_UNLIMITED
24562483

24572484
Constant used to indicate the LRU garbage collection should be disabled. Set this value as the `cacheSizeBytes` on the settings passed to the [Firestore](./firestore_.firestore.md#firestore_class) instance.

docs-devsite/firestore_lite.md

+27
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ https://github.com/firebase/firebase-js-sdk
8989
| [endBefore(snapshot)](./firestore_lite.md#endbefore_9a4477f) | Creates a [QueryEndAtConstraint](./firestore_.queryendatconstraint.md#queryendatconstraint_class) that modifies the result set to end before the provided document (exclusive). The end position is relative to the order of the query. The document must contain all of the fields provided in the orderBy of the query. |
9090
| [startAfter(snapshot)](./firestore_lite.md#startafter_9a4477f) | Creates a [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) that modifies the result set to start after the provided document (exclusive). The starting position is relative to the order of the query. The document must contain all of the fields provided in the orderBy of the query. |
9191
| [startAt(snapshot)](./firestore_lite.md#startat_9a4477f) | Creates a [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) that modifies the result set to start at the provided document (inclusive). The starting position is relative to the order of the query. The document must contain all of the fields provided in the <code>orderBy</code> of this query. |
92+
| <b>function(values, ...)</b> |
93+
| [vector(values)](./firestore_lite.md#vector_0dbdaf2) | Creates a new <code>VectorValue</code> constructed with a copy of the given array of numbers. |
9294

9395
## Classes
9496

@@ -117,6 +119,7 @@ https://github.com/firebase/firebase-js-sdk
117119
| [QueryStartAtConstraint](./firestore_lite.querystartatconstraint.md#querystartatconstraint_class) | A <code>QueryStartAtConstraint</code> is used to exclude documents from the start of a result set returned by a Firestore query. <code>QueryStartAtConstraint</code>s are created by invoking [startAt()](./firestore_.md#startat_9a4477f) or [startAfter()](./firestore_.md#startafter_9a4477f) and can then be passed to [query()](./firestore_.md#query_9f7b0f4) to create a new query instance that also contains this <code>QueryStartAtConstraint</code>. |
118120
| [Timestamp](./firestore_lite.timestamp.md#timestamp_class) | A <code>Timestamp</code> represents a point in time independent of any time zone or calendar, represented as seconds and fractions of seconds at nanosecond resolution in UTC Epoch time.<!-- -->It is encoded using the Proleptic Gregorian Calendar which extends the Gregorian calendar backwards to year one. It is encoded assuming all minutes are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.<!-- -->For examples and further specifications, refer to the [Timestamp definition](https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto)<!-- -->. |
119121
| [Transaction](./firestore_lite.transaction.md#transaction_class) | A reference to a transaction.<!-- -->The <code>Transaction</code> object passed to a transaction's <code>updateFunction</code> provides the methods to read and write data within the transaction context. See [runTransaction()](./firestore_.md#runtransaction_6f03ec4)<!-- -->. |
122+
| [VectorValue](./firestore_lite.vectorvalue.md#vectorvalue_class) | Represents a vector type in Firestore documents. Create an instance with . VectorValue |
120123
| [WriteBatch](./firestore_lite.writebatch.md#writebatch_class) | A write batch, used to perform multiple writes as a single atomic unit.<!-- -->A <code>WriteBatch</code> object can be acquired by calling [writeBatch()](./firestore_.md#writebatch_231a8e0)<!-- -->. It provides methods for adding writes to the write batch. None of the writes will be committed (or visible locally) until [WriteBatch.commit()](./firestore_.writebatch.md#writebatchcommit) is called. |
121124

122125
## Interfaces
@@ -1568,6 +1571,30 @@ export declare function startAt<AppModelType, DbModelType extends DocumentData>(
15681571

15691572
A [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) to pass to `query()`<!-- -->.
15701573

1574+
## function(values, ...)
1575+
1576+
### vector(values) {:#vector_0dbdaf2}
1577+
1578+
Creates a new `VectorValue` constructed with a copy of the given array of numbers.
1579+
1580+
<b>Signature:</b>
1581+
1582+
```typescript
1583+
export declare function vector(values?: number[]): VectorValue;
1584+
```
1585+
1586+
#### Parameters
1587+
1588+
| Parameter | Type | Description |
1589+
| --- | --- | --- |
1590+
| values | number\[\] | Create a <code>VectorValue</code> instance with a copy of this array of numbers. |
1591+
1592+
<b>Returns:</b>
1593+
1594+
[VectorValue](./firestore_lite.vectorvalue.md#vectorvalue_class)
1595+
1596+
A new `VectorValue` constructed with a copy of the given array of numbers.
1597+
15711598
## AddPrefixToKeys
15721599

15731600
Returns a new map where every key is prefixed with the outer key appended to a dot.

packages/firestore/lite/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ export {
127127
arrayRemove,
128128
arrayUnion,
129129
serverTimestamp,
130-
deleteField
130+
deleteField,
131+
vector
131132
} from '../src/lite-api/field_value_impl';
132133

133134
export {
@@ -138,6 +139,8 @@ export {
138139
snapshotEqual
139140
} from '../src/lite-api/snapshot';
140141

142+
export { VectorValue } from '../src/lite-api/vector_value';
143+
141144
export { WriteBatch, writeBatch } from '../src/lite-api/write_batch';
142145

143146
export { TransactionOptions } from '../src/lite-api/transaction_options';

packages/firestore/src/api.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,12 @@ export {
172172
arrayUnion,
173173
deleteField,
174174
increment,
175-
serverTimestamp
175+
serverTimestamp,
176+
vector
176177
} from './api/field_value_impl';
177178

179+
export { VectorValue } from './lite-api/vector_value';
180+
178181
export { LogLevelString as LogLevel, setLogLevel } from './util/log';
179182

180183
export { Bytes } from './api/bytes';

packages/firestore/src/api/field_value_impl.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ export {
2020
arrayRemove,
2121
arrayUnion,
2222
serverTimestamp,
23-
deleteField
23+
deleteField,
24+
vector
2425
} from '../lite-api/field_value_impl';

packages/firestore/src/index/firestore_index_value_writer.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ import {
2121
normalizeNumber,
2222
normalizeTimestamp
2323
} from '../model/normalize';
24-
import { isMaxValue } from '../model/values';
24+
import {
25+
isVectorValue,
26+
VECTOR_MAP_VECTORS_KEY,
27+
isMaxValue
28+
} from '../model/values';
2529
import { ArrayValue, MapValue, Value } from '../protos/firestore_proto_api';
2630
import { fail } from '../util/assert';
2731
import { isNegativeZero } from '../util/types';
@@ -41,6 +45,7 @@ const INDEX_TYPE_BLOB = 30;
4145
const INDEX_TYPE_REFERENCE = 37;
4246
const INDEX_TYPE_GEOPOINT = 45;
4347
const INDEX_TYPE_ARRAY = 50;
48+
const INDEX_TYPE_VECTOR = 53;
4449
const INDEX_TYPE_MAP = 55;
4550
const INDEX_TYPE_REFERENCE_SEGMENT = 60;
4651

@@ -121,6 +126,8 @@ export class FirestoreIndexValueWriter {
121126
} else if ('mapValue' in indexValue) {
122127
if (isMaxValue(indexValue)) {
123128
this.writeValueTypeLabel(encoder, Number.MAX_SAFE_INTEGER);
129+
} else if (isVectorValue(indexValue)) {
130+
this.writeIndexVector(indexValue.mapValue!, encoder);
124131
} else {
125132
this.writeIndexMap(indexValue.mapValue!, encoder);
126133
this.writeTruncationMarker(encoder);
@@ -160,6 +167,24 @@ export class FirestoreIndexValueWriter {
160167
}
161168
}
162169

170+
private writeIndexVector(
171+
mapIndexValue: MapValue,
172+
encoder: DirectionalIndexByteEncoder
173+
): void {
174+
const map = mapIndexValue.fields || {};
175+
this.writeValueTypeLabel(encoder, INDEX_TYPE_VECTOR);
176+
177+
// Vectors sort first by length
178+
const key = VECTOR_MAP_VECTORS_KEY;
179+
const length = map[key].arrayValue?.values?.length || 0;
180+
this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
181+
encoder.writeNumber(normalizeNumber(length));
182+
183+
// Vectors then sort by position value
184+
this.writeIndexString(key, encoder);
185+
this.writeIndexValueAux(map[key], encoder);
186+
}
187+
163188
private writeIndexArray(
164189
arrayIndexValue: ArrayValue,
165190
encoder: DirectionalIndexByteEncoder

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

+12
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

+42-2
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,17 @@ import {
4141
NumericIncrementTransformOperation,
4242
ServerTimestampTransform
4343
} from '../model/transform_operation';
44+
import {
45+
TYPE_KEY,
46+
VECTOR_MAP_VECTORS_KEY,
47+
VECTOR_VALUE_SENTINEL
48+
} from '../model/values';
4449
import { newSerializer } from '../platform/serializer';
4550
import {
4651
MapValue as ProtoMapValue,
4752
Value as ProtoValue
4853
} from '../protos/firestore_proto_api';
49-
import { toNumber } from '../remote/number_serializer';
54+
import { toDouble, toNumber } from '../remote/number_serializer';
5055
import {
5156
JsonProtoSerializer,
5257
toBytes,
@@ -69,6 +74,7 @@ import {
6974
WithFieldValue
7075
} from './reference';
7176
import { Timestamp } from './timestamp';
77+
import { VectorValue } from './vector_value';
7278

7379
const RESERVED_FIELD_REGEX = /^__.*__$/;
7480

@@ -901,13 +907,46 @@ function parseScalarValue(
901907
value._key.path
902908
)
903909
};
910+
} else if (value instanceof VectorValue) {
911+
return parseVectorValue(value, context);
904912
} else {
905913
throw context.createError(
906914
`Unsupported field value: ${valueDescription(value)}`
907915
);
908916
}
909917
}
910918

919+
/**
920+
* Creates a new VectorValue proto value (using the internal format).
921+
*/
922+
export function parseVectorValue(
923+
value: VectorValue,
924+
context: ParseContextImpl
925+
): ProtoValue {
926+
const mapValue: ProtoMapValue = {
927+
fields: {
928+
[TYPE_KEY]: {
929+
stringValue: VECTOR_VALUE_SENTINEL
930+
},
931+
[VECTOR_MAP_VECTORS_KEY]: {
932+
arrayValue: {
933+
values: value.toArray().map(value => {
934+
if (typeof value !== 'number') {
935+
throw context.createError(
936+
'VectorValues must only contain numeric values.'
937+
);
938+
}
939+
940+
return toDouble(context.serializer, value);
941+
})
942+
}
943+
}
944+
}
945+
};
946+
947+
return { mapValue };
948+
}
949+
911950
/**
912951
* Checks whether an object looks like a JSON object that should be converted
913952
* into a struct. Normal class/prototype instances are considered to look like
@@ -925,7 +964,8 @@ function looksLikeJsonObject(input: unknown): boolean {
925964
!(input instanceof GeoPoint) &&
926965
!(input instanceof Bytes) &&
927966
!(input instanceof DocumentReference) &&
928-
!(input instanceof FieldValue)
967+
!(input instanceof FieldValue) &&
968+
!(input instanceof VectorValue)
929969
);
930970
}
931971

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

+17-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
getPreviousValue
3131
} from '../model/server_timestamps';
3232
import { TypeOrder } from '../model/type_order';
33-
import { typeOrder } from '../model/values';
33+
import { VECTOR_MAP_VECTORS_KEY, typeOrder } from '../model/values';
3434
import {
3535
ApiClientObjectMap,
3636
ArrayValue as ProtoArrayValue,
@@ -48,6 +48,7 @@ import { forEach } from '../util/obj';
4848

4949
import { GeoPoint } from './geo_point';
5050
import { Timestamp } from './timestamp';
51+
import { VectorValue } from './vector_value';
5152

5253
export type ServerTimestampBehavior = 'estimate' | 'previous' | 'none';
5354

@@ -85,6 +86,8 @@ export abstract class AbstractUserDataWriter {
8586
return this.convertArray(value.arrayValue!, serverTimestampBehavior);
8687
case TypeOrder.ObjectValue:
8788
return this.convertObject(value.mapValue!, serverTimestampBehavior);
89+
case TypeOrder.VectorValue:
90+
return this.convertVectorValue(value.mapValue!);
8891
default:
8992
throw fail('Invalid value type: ' + JSON.stringify(value));
9093
}
@@ -111,6 +114,19 @@ export abstract class AbstractUserDataWriter {
111114
return result;
112115
}
113116

117+
/**
118+
* @internal
119+
*/
120+
convertVectorValue(mapValue: ProtoMapValue): VectorValue {
121+
const values = mapValue.fields?.[
122+
VECTOR_MAP_VECTORS_KEY
123+
].arrayValue?.values?.map(value => {
124+
return normalizeNumber(value.doubleValue);
125+
});
126+
127+
return new VectorValue(values);
128+
}
129+
114130
private convertGeoPoint(value: ProtoLatLng): GeoPoint {
115131
return new GeoPoint(
116132
normalizeNumber(value.latitude),

0 commit comments

Comments
 (0)