From 7ee19558b99b4c158052742080f892d951226a11 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Thu, 2 May 2024 11:47:49 -0600 Subject: [PATCH 01/15] Implement VectorValue type support. --- .../src/lite-api/field_value_impl.ts | 12 ++ .../src/lite-api/user_data_reader.ts | 8 +- .../src/lite-api/user_data_writer.ts | 17 ++ .../firestore/src/lite-api/vector_value.ts | 34 ++++ packages/firestore/src/model/type_order.ts | 3 +- packages/firestore/src/model/values.ts | 26 +++ packages/firestore/src/util/array.ts | 54 +++++- .../test/integration/api/database.test.ts | 173 +++++++++++++++++- 8 files changed, 322 insertions(+), 5 deletions(-) create mode 100644 packages/firestore/src/lite-api/vector_value.ts diff --git a/packages/firestore/src/lite-api/field_value_impl.ts b/packages/firestore/src/lite-api/field_value_impl.ts index b4c65ca6de2..2c910bdace5 100644 --- a/packages/firestore/src/lite-api/field_value_impl.ts +++ b/packages/firestore/src/lite-api/field_value_impl.ts @@ -23,6 +23,7 @@ import { NumericIncrementFieldValueImpl, ServerTimestampFieldValueImpl } from './user_data_reader'; +import { VectorValue } from './vector_value'; /** * Returns a sentinel for use with {@link @firebase/firestore/lite#(updateDoc:1)} or @@ -97,3 +98,14 @@ export function arrayRemove(...elements: unknown[]): FieldValue { export function increment(n: number): FieldValue { return new NumericIncrementFieldValueImpl('increment', n); } + +/** + * Creates a new `VectorValue` constructed with a copy of the given array of numbers. + * + * @param values - Create a `VectorValue` instance with a copy of this array of numbers. + * + * @returns A new `VectorValue` constructed with a copy of the given array of numbers. + */ +export function vector(values?: number[]): VectorValue { + return new VectorValue(values); +} diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index 008eb49809c..4c5f3578294 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -26,6 +26,7 @@ import { ParseContext } from '../api/parse_context'; import { DatabaseId } from '../core/database_info'; import { DocumentKey } from '../model/document_key'; import { FieldMask } from '../model/field_mask'; +import { vectorValue } from '../model/map_type'; import { FieldTransform, Mutation, @@ -69,6 +70,7 @@ import { WithFieldValue } from './reference'; import { Timestamp } from './timestamp'; +import { VectorValue } from './vector_value'; const RESERVED_FIELD_REGEX = /^__.*__$/; @@ -901,6 +903,9 @@ function parseScalarValue( value._key.path ) }; + } + if (value instanceof VectorValue) { + return vectorValue(value); } else { throw context.createError( `Unsupported field value: ${valueDescription(value)}` @@ -925,7 +930,8 @@ function looksLikeJsonObject(input: unknown): boolean { !(input instanceof GeoPoint) && !(input instanceof Bytes) && !(input instanceof DocumentReference) && - !(input instanceof FieldValue) + !(input instanceof FieldValue) && + !(input instanceof VectorValue) ); } diff --git a/packages/firestore/src/lite-api/user_data_writer.ts b/packages/firestore/src/lite-api/user_data_writer.ts index 5536b5c8b42..893fe6f5a60 100644 --- a/packages/firestore/src/lite-api/user_data_writer.ts +++ b/packages/firestore/src/lite-api/user_data_writer.ts @@ -19,6 +19,7 @@ import { DocumentData } from '@firebase/firestore-types'; import { DatabaseId } from '../core/database_info'; import { DocumentKey } from '../model/document_key'; +import { VECTOR_MAP_VECTORS_KEY } from '../model/map_type'; import { normalizeByteString, normalizeNumber, @@ -48,6 +49,7 @@ import { forEach } from '../util/obj'; import { GeoPoint } from './geo_point'; import { Timestamp } from './timestamp'; +import { VectorValue } from './vector_value'; export type ServerTimestampBehavior = 'estimate' | 'previous' | 'none'; @@ -85,6 +87,8 @@ export abstract class AbstractUserDataWriter { return this.convertArray(value.arrayValue!, serverTimestampBehavior); case TypeOrder.ObjectValue: return this.convertObject(value.mapValue!, serverTimestampBehavior); + case TypeOrder.VectorValue: + return this.convertVectorValue(value.mapValue!); default: throw fail('Invalid value type: ' + JSON.stringify(value)); } @@ -111,6 +115,19 @@ export abstract class AbstractUserDataWriter { return result; } + /** + * @internal + */ + convertVectorValue(mapValue: ProtoMapValue): VectorValue { + const values = mapValue.fields?.[ + VECTOR_MAP_VECTORS_KEY + ].arrayValue?.values?.map(value => { + return normalizeNumber(value.doubleValue); + }); + + return new VectorValue(values); + } + private convertGeoPoint(value: ProtoLatLng): GeoPoint { return new GeoPoint( normalizeNumber(value.latitude), diff --git a/packages/firestore/src/lite-api/vector_value.ts b/packages/firestore/src/lite-api/vector_value.ts new file mode 100644 index 00000000000..7f6d36042c0 --- /dev/null +++ b/packages/firestore/src/lite-api/vector_value.ts @@ -0,0 +1,34 @@ +import { isPrimitiveArrayEqual } from '../util/array'; + +/** + * Represent a vector type in Firestore documents. + * Create an instance with {@link FieldValue.vector}. + * + * @class VectorValue + */ +export class VectorValue { + private readonly _values: number[]; + + /** + * @private + * @internal + */ + constructor(values: number[] | undefined) { + // Making a copy of the parameter. + this._values = (values || []).map(n => n); + } + + /** + * Returns a copy of the raw number array form of the vector. + */ + toArray(): number[] { + return this._values.map(n => n); + } + + /** + * Returns `true` if the two VectorValue has the same raw number arrays, returns `false` otherwise. + */ + isEqual(other: VectorValue): boolean { + return isPrimitiveArrayEqual(this._values, other._values); + } +} diff --git a/packages/firestore/src/model/type_order.ts b/packages/firestore/src/model/type_order.ts index df4c6067a31..749b8e8036d 100644 --- a/packages/firestore/src/model/type_order.ts +++ b/packages/firestore/src/model/type_order.ts @@ -35,6 +35,7 @@ export const enum TypeOrder { RefValue = 7, GeoPointValue = 8, ArrayValue = 9, - ObjectValue = 10, + VectorValue = 10, + ObjectValue = 11, MaxValue = 9007199254740991 // Number.MAX_SAFE_INTEGER } diff --git a/packages/firestore/src/model/values.ts b/packages/firestore/src/model/values.ts index 3466c2c1ee6..1d3b6b2c6cc 100644 --- a/packages/firestore/src/model/values.ts +++ b/packages/firestore/src/model/values.ts @@ -29,6 +29,7 @@ import { forEach, objectSize } from '../util/obj'; import { isNegativeZero } from '../util/types'; import { DocumentKey } from './document_key'; +import { isVectorValue, VECTOR_MAP_VECTORS_KEY } from './map_type'; import { normalizeByteString, normalizeNumber, @@ -79,6 +80,8 @@ export function typeOrder(value: Value): TypeOrder { return TypeOrder.ServerTimestampValue; } else if (isMaxValue(value)) { return TypeOrder.MaxValue; + } else if (isVectorValue(value)) { + return TypeOrder.VectorValue; } return TypeOrder.ObjectValue; } else { @@ -123,6 +126,7 @@ export function valueEquals(left: Value, right: Value): boolean { right.arrayValue!.values || [], valueEquals ); + case TypeOrder.VectorValue: case TypeOrder.ObjectValue: return objectEquals(left, right); case TypeOrder.MaxValue: @@ -252,6 +256,8 @@ export function valueCompare(left: Value, right: Value): number { return compareGeoPoints(left.geoPointValue!, right.geoPointValue!); case TypeOrder.ArrayValue: return compareArrays(left.arrayValue!, right.arrayValue!); + case TypeOrder.VectorValue: + return compareVectors(left.mapValue!, right.mapValue!); case TypeOrder.ObjectValue: return compareMaps(left.mapValue!, right.mapValue!); default: @@ -349,6 +355,25 @@ function compareArrays(left: ArrayValue, right: ArrayValue): number { return primitiveComparator(leftArray.length, rightArray.length); } +function compareVectors(left: MapValue, right: MapValue): number { + const leftMap = left.fields || {}; + const rightMap = right.fields || {}; + + // The vector is a map, but only vector value is compared. + const leftArrayValue = leftMap[VECTOR_MAP_VECTORS_KEY]?.arrayValue; + const rightArrayValue = rightMap[VECTOR_MAP_VECTORS_KEY]?.arrayValue; + + const lengthCompare = primitiveComparator( + leftArrayValue?.values?.length || 0, + rightArrayValue?.values?.length || 0 + ); + if (lengthCompare !== 0) { + return lengthCompare; + } + + return compareArrays(leftArrayValue!, rightArrayValue!); +} + function compareMaps(left: MapValue, right: MapValue): number { if (left === MAX_VALUE.mapValue && right === MAX_VALUE.mapValue) { return 0; @@ -504,6 +529,7 @@ export function estimateByteSize(value: Value): number { return 16; case TypeOrder.ArrayValue: return estimateArrayByteSize(value.arrayValue!); + case TypeOrder.VectorValue: case TypeOrder.ObjectValue: return estimateMapByteSize(value.mapValue!); default: diff --git a/packages/firestore/src/util/array.ts b/packages/firestore/src/util/array.ts index f09f67da33b..3427abcf67b 100644 --- a/packages/firestore/src/util/array.ts +++ b/packages/firestore/src/util/array.ts @@ -28,7 +28,7 @@ export function includes(array: T[], value: T): boolean { } /** - * Returns true iff the array contains any value mathching the predicate + * Returns true iff the array contains any value matching the predicate */ export function some(array: T[], predicate: (t: T) => boolean): boolean { for (let i = 0; i < array.length; i++) { @@ -111,3 +111,55 @@ export function diffArrays( onRemove(before[b++]); } } + +/** + * Verifies equality for an array of objects using the `isEqual` interface. + * + * @private + * @internal + * @param left Array of objects supporting `isEqual`. + * @param right Array of objects supporting `isEqual`. + * @return True if arrays are equal. + */ +export function isArrayEqual boolean }>( + left: T[], + right: T[] +): boolean { + if (left.length !== right.length) { + return false; + } + + for (let i = 0; i < left.length; ++i) { + if (!left[i].isEqual(right[i])) { + return false; + } + } + + return true; +} + +/** + * Verifies equality for an array of primitives. + * + * @private + * @internal + * @param left Array of primitives. + * @param right Array of primitives. + * @return True if arrays are equal. + */ +export function isPrimitiveArrayEqual( + left: T[], + right: T[] +): boolean { + if (left.length !== right.length) { + return false; + } + + for (let i = 0; i < left.length; ++i) { + if (left[i] !== right[i]) { + return false; + } + } + + return true; +} diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 70b9a78fe18..f3268c15b21 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -20,6 +20,7 @@ import { Deferred } from '@firebase/util'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import { vector } from '../../../src/lite-api/field_value_impl'; import { EventsAccumulator } from '../util/events_accumulator'; import { addDoc, @@ -63,7 +64,8 @@ import { FieldPath, newTestFirestore, SnapshotOptions, - newTestApp + newTestApp, + QuerySnapshot } from '../util/firebase_export'; import { apiDescribe, @@ -73,7 +75,8 @@ import { withTestDbs, withTestDoc, withTestDocAndInitialData, - withNamedTestDbsOrSkipUnlessUsingEmulator + withNamedTestDbsOrSkipUnlessUsingEmulator, + toDataArray } from '../util/helpers'; import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings'; @@ -576,6 +579,172 @@ apiDescribe('Database', persistence => { }); }); + describe('vector embeddings', () => { + it('can write and read vector embeddings', async () => { + return withTestCollection(persistence, {}, async coll => { + const ref = await addDoc(coll, { + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]) + }); + await setDoc(ref, { + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]), + vector2: vector([0, 0, 0]) + }); + await updateDoc(ref, { + vector3: vector([-1, -200, -999]) + }); + + const snap1 = await getDoc(ref); + expect(snap1.get('vector0').isEqual(vector([0.0]))).to.be.true; + expect(snap1.get('vector1').isEqual(vector([1, 2, 3.99]))).to.be.true; + expect(snap1.get('vector2').isEqual(vector([0, 0, 0]))).to.be.true; + expect(snap1.get('vector3').isEqual(vector([-1, -200, -999]))).to.be + .true; + }); + }); + + it('can listen to documents with vectors', async () => { + return withTestCollection(persistence, {}, async randomCol => { + const ref = doc(randomCol); + const initialDeferred = new Deferred(); + const createDeferred = new Deferred(); + const setDeferred = new Deferred(); + const updateDeferred = new Deferred(); + const deleteDeferred = new Deferred(); + + const expected = [ + initialDeferred, + createDeferred, + setDeferred, + updateDeferred, + deleteDeferred + ]; + let idx = 0; + let document: DocumentSnapshot | null = null; + + const q = query(randomCol, where('purpose', '==', 'vector tests')); + const unlisten = onSnapshot(q, (snap: QuerySnapshot) => { + expected[idx].resolve(); + idx += 1; + if (snap.docs.length > 0) { + document = snap.docs[0]; + } else { + document = null; + } + }); + + await initialDeferred.promise; + expect(document).to.be.null; + + await setDoc(ref, { + purpose: 'vector tests', + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]) + }); + + await createDeferred.promise; + expect(document).to.be.not.null; + expect(document!.get('vector0').isEqual(vector([0.0]))).to.be.true; + expect(document!.get('vector1').isEqual(vector([1, 2, 3.99]))).to.be + .true; + + await setDoc(ref, { + purpose: 'vector tests', + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]), + vector2: vector([0, 0, 0]) + }); + await setDeferred.promise; + expect(document).to.be.not.null; + expect(document!.get('vector0').isEqual(vector([0.0]))).to.be.true; + expect(document!.get('vector1').isEqual(vector([1, 2, 3.99]))).to.be + .true; + expect(document!.get('vector2').isEqual(vector([0, 0, 0]))).to.be.true; + + await updateDoc(ref, { + vector3: vector([-1, -200, -999]) + }); + await updateDeferred.promise; + expect(document).to.be.not.null; + expect(document!.get('vector0').isEqual(vector([0.0]))).to.be.true; + expect(document!.get('vector1').isEqual(vector([1, 2, 3.99]))).to.be + .true; + expect(document!.get('vector2').isEqual(vector([0, 0, 0]))).to.be.true; + expect(document!.get('vector3').isEqual(vector([-1, -200, -999]))).to.be + .true; + + await deleteDoc(ref); + await deleteDeferred.promise; + expect(document).to.be.null; + + unlisten(); + }); + }); + + it('SDK orders vector field same way as backend', async () => { + // Test data in the order that we expect the backend to sort it. + const docsInOrder = [ + { embedding: [1, 2, 3, 4, 5, 6] }, + { embedding: [100] }, + { embedding: vector([Number.NEGATIVE_INFINITY]) }, + { embedding: vector([-100]) }, + { embedding: vector([100]) }, + { embedding: vector([Number.POSITIVE_INFINITY]) }, + { embedding: vector([1, 2]) }, + { embedding: vector([2, 2]) }, + { embedding: vector([1, 2, 3]) }, + { embedding: vector([1, 2, 3, 4]) }, + { embedding: vector([1, 2, 3, 4, 5]) }, + { embedding: vector([1, 2, 100, 4, 4]) }, + { embedding: vector([100, 2, 3, 4, 5]) }, + { embedding: { HELLO: 'WORLD' } }, + { embedding: { hello: 'world' } } + ]; + + const docs = docsInOrder.reduce((obj, doc) => { + obj[Math.random().toString()] = doc; + return obj; + }, {} as { [i: string]: DocumentData }); + + return withTestCollection(persistence, docs, async randomCol => { + // We validate that the SDK orders the vector field the same way as the backend + // by comparing the sort order of vector fields from a Query.get() and + // Query.onSnapshot(). Query.onSnapshot() will return sort order of the SDK, + // and Query.get() will return sort order of the backend. + + const orderedQuery = query(randomCol, orderBy('embedding')); + const gotInitialSnapshot = new Deferred(); + + const unsubscribe = onSnapshot( + orderedQuery, + snapshot => { + gotInitialSnapshot.resolve(snapshot); + }, + err => { + gotInitialSnapshot.reject!(err); + } + ); + + const watchSnapshot = await gotInitialSnapshot.promise; + unsubscribe(); + + const getSnapshot = await getDocs(orderedQuery); + + // Compare the snapshot (including sort order) of a snapshot + // from Query.onSnapshot() to an actual snapshot from Query.get() + expect(toDataArray(watchSnapshot)).to.deep.equal( + toDataArray(getSnapshot) + ); + + // Compare the snapshot (including sort order) of a snapshot + // from Query.onSnapshot() to the expected sort order from + // the backend. + expect(toDataArray(watchSnapshot)).to.deep.equal(docsInOrder); + }); + }); + }); + describe('documents: ', () => { const invalidDocValues = [undefined, null, 0, 'foo', ['a'], new Date()]; for (const val of invalidDocValues) { From 17c0c15c9308c4bbfa97c9e87ce20db08ced9cfd Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 3 May 2024 09:05:41 -0600 Subject: [PATCH 02/15] Support vectors in client side index. --- .../src/index/firestore_index_value_writer.ts | 22 ++++++ .../test/integration/api/database.test.ts | 60 ++++++++++++++- .../test/integration/util/helpers.ts | 3 +- .../firestore_index_value_writer.test.ts | 77 +++++++++++++++++++ .../test/unit/local/index_manager.test.ts | 33 ++++++++ 5 files changed, 193 insertions(+), 2 deletions(-) diff --git a/packages/firestore/src/index/firestore_index_value_writer.ts b/packages/firestore/src/index/firestore_index_value_writer.ts index 9d5d86ab640..033a30ef78f 100644 --- a/packages/firestore/src/index/firestore_index_value_writer.ts +++ b/packages/firestore/src/index/firestore_index_value_writer.ts @@ -16,6 +16,7 @@ */ import { DocumentKey } from '../model/document_key'; +import { isVectorValue, VECTOR_MAP_VECTORS_KEY } from '../model/map_type'; import { normalizeByteString, normalizeNumber, @@ -41,6 +42,7 @@ const INDEX_TYPE_BLOB = 30; const INDEX_TYPE_REFERENCE = 37; const INDEX_TYPE_GEOPOINT = 45; const INDEX_TYPE_ARRAY = 50; +const INDEX_TYPE_VECTOR = 53; const INDEX_TYPE_MAP = 55; const INDEX_TYPE_REFERENCE_SEGMENT = 60; @@ -121,6 +123,8 @@ export class FirestoreIndexValueWriter { } else if ('mapValue' in indexValue) { if (isMaxValue(indexValue)) { this.writeValueTypeLabel(encoder, Number.MAX_SAFE_INTEGER); + } else if (isVectorValue(indexValue)) { + this.writeIndexVector(indexValue.mapValue!, encoder); } else { this.writeIndexMap(indexValue.mapValue!, encoder); this.writeTruncationMarker(encoder); @@ -160,6 +164,24 @@ export class FirestoreIndexValueWriter { } } + private writeIndexVector( + mapIndexValue: MapValue, + encoder: DirectionalIndexByteEncoder + ): void { + const map = mapIndexValue.fields || {}; + this.writeValueTypeLabel(encoder, INDEX_TYPE_VECTOR); + + // Vectors sort first by length + const key = VECTOR_MAP_VECTORS_KEY; + const length = map[key].arrayValue?.values?.length || 0; + this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER); + encoder.writeNumber(normalizeNumber(length)); + + // Vectors then sort by position value + this.writeIndexString(key, encoder); + this.writeIndexValueAux(map[key], encoder); + } + private writeIndexArray( arrayIndexValue: ArrayValue, encoder: DirectionalIndexByteEncoder diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index f3268c15b21..2d802265cad 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -76,7 +76,8 @@ import { withTestDoc, withTestDocAndInitialData, withNamedTestDbsOrSkipUnlessUsingEmulator, - toDataArray + toDataArray, + checkOnlineAndOfflineResultsMatch } from '../util/helpers'; import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings'; @@ -743,6 +744,63 @@ apiDescribe('Database', persistence => { expect(toDataArray(watchSnapshot)).to.deep.equal(docsInOrder); }); }); + + // eslint-disable-next-line no-restricted-properties + (persistence.gc === 'lru' ? describe : describe.skip)('From Cache', () => { + it('SDK orders vector field the same way online and offline', async () => { + // Test data in the order that we expect the SDK to sort it. + const docsInOrder = [ + { embedding: [1, 2, 3, 4, 5, 6] }, + { embedding: [100] }, + { embedding: vector([Number.NEGATIVE_INFINITY]) }, + { embedding: vector([-100]) }, + { embedding: vector([100]) }, + { embedding: vector([Number.POSITIVE_INFINITY]) }, + { embedding: vector([1, 2]) }, + { embedding: vector([2, 2]) }, + { embedding: vector([1, 2, 3]) }, + { embedding: vector([1, 2, 3, 4]) }, + { embedding: vector([1, 2, 3, 4, 5]) }, + { embedding: vector([1, 2, 100, 4, 4]) }, + { embedding: vector([100, 2, 3, 4, 5]) }, + { embedding: { HELLO: 'WORLD' } }, + { embedding: { hello: 'world' } } + ]; + + const documentIds: string[] = []; + const docs = docsInOrder.reduce((obj, doc, index) => { + const documentId = index.toString(); + documentIds.push(documentId); + obj[documentId] = doc; + return obj; + }, {} as { [i: string]: DocumentData }); + + return withTestCollection(persistence, docs, async randomCol => { + const orderedQuery = query(randomCol, orderBy('embedding')); + await checkOnlineAndOfflineResultsMatch(orderedQuery, ...documentIds); + + const orderedQueryLessThan = query( + randomCol, + orderBy('embedding'), + where('embedding', '<', vector([1, 2, 100, 4, 4])) + ); + await checkOnlineAndOfflineResultsMatch( + orderedQueryLessThan, + ...documentIds.slice(2, 11) + ); + + const orderedQueryGreaterThan = query( + randomCol, + orderBy('embedding'), + where('embedding', '>', vector([1, 2, 100, 4, 4])) + ); + await checkOnlineAndOfflineResultsMatch( + orderedQueryGreaterThan, + ...documentIds.slice(12, 13) + ); + }); + }); + }); }); describe('documents: ', () => { diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 647360db463..bdfc7e7daa5 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -182,7 +182,8 @@ function apiDescribeInternal( testSuite: (persistence: PersistenceMode) => void ): void { const persistenceModes: PersistenceMode[] = [ - new MemoryEagerPersistenceMode() + new MemoryEagerPersistenceMode(), + new MemoryLruPersistenceMode() ]; if (isPersistenceAvailable()) { persistenceModes.push(new IndexedDbPersistenceMode()); diff --git a/packages/firestore/test/unit/index/firestore_index_value_writer.test.ts b/packages/firestore/test/unit/index/firestore_index_value_writer.test.ts index 8f7cd7c973f..8daa97eb77d 100644 --- a/packages/firestore/test/unit/index/firestore_index_value_writer.test.ts +++ b/packages/firestore/test/unit/index/firestore_index_value_writer.test.ts @@ -169,5 +169,82 @@ describe('Firestore Index Value Writer', () => { compareIndexEncodedValues(value3, value4, IndexKind.DESCENDING) ).to.equal(-1); }); + + it('sorts vector as a different type from array and map, with unique rules', () => { + const vector1 = { + mapValue: { + fields: { + '__type__': { stringValue: '__vector__' }, + 'value': { + arrayValue: { values: [{ doubleValue: 100 }] } + } + } + } + }; + const vector2 = { + mapValue: { + fields: { + '__type__': { stringValue: '__vector__' }, + 'value': { + arrayValue: { values: [{ doubleValue: 1 }, { doubleValue: 2 }] } + } + } + } + }; + const vector3 = { + mapValue: { + fields: { + '__type__': { stringValue: '__vector__' }, + 'value': { + arrayValue: { values: [{ doubleValue: 1 }, { doubleValue: 3 }] } + } + } + } + }; + const map1 = { + mapValue: { + fields: { + 'value': { + arrayValue: { values: [{ doubleValue: 1 }, { doubleValue: 2 }] } + } + } + } + }; + const array1 = { + arrayValue: { values: [{ doubleValue: 1 }, { doubleValue: 2 }] } + }; + + // Array sorts before vector + expect( + compareIndexEncodedValues(array1, vector1, IndexKind.ASCENDING) + ).to.equal(-1); + expect( + compareIndexEncodedValues(array1, vector1, IndexKind.DESCENDING) + ).to.equal(1); + + // Vector sorts before map + expect( + compareIndexEncodedValues(vector3, map1, IndexKind.ASCENDING) + ).to.equal(-1); + expect( + compareIndexEncodedValues(vector3, map1, IndexKind.DESCENDING) + ).to.equal(1); + + // Shorter vectors sort before longer vectors + expect( + compareIndexEncodedValues(vector1, vector2, IndexKind.ASCENDING) + ).to.equal(-1); + expect( + compareIndexEncodedValues(vector1, vector2, IndexKind.DESCENDING) + ).to.equal(1); + + // Vectors of the same length sort by value + expect( + compareIndexEncodedValues(vector2, vector3, IndexKind.ASCENDING) + ).to.equal(-1); + expect( + compareIndexEncodedValues(vector2, vector3, IndexKind.DESCENDING) + ).to.equal(1); + }); }); }); diff --git a/packages/firestore/test/unit/local/index_manager.test.ts b/packages/firestore/test/unit/local/index_manager.test.ts index 5d27fcc257f..ee8eba60ff0 100644 --- a/packages/firestore/test/unit/local/index_manager.test.ts +++ b/packages/firestore/test/unit/local/index_manager.test.ts @@ -30,6 +30,7 @@ import { queryWithLimit, queryWithStartAt } from '../../../src/core/query'; +import { vector } from '../../../src/lite-api/field_value_impl'; import { Timestamp } from '../../../src/lite-api/timestamp'; import { displayNameForIndexType, @@ -1100,6 +1101,38 @@ describe('IndexedDbIndexManager', async () => { await verifyResults(q, 'coll/val6', 'coll/val3', 'coll/val4', 'coll/val5'); }); + it('can index VectorValue fields', async () => { + await indexManager.addFieldIndex( + fieldIndex('coll', { fields: [['embedding', IndexKind.ASCENDING]] }) + ); + + await addDoc('coll/arr1', { 'embedding': [1, 2, 3] }); + await addDoc('coll/map2', { 'embedding': {} }); + await addDoc('coll/doc3', { 'embedding': vector([4, 5, 6]) }); + await addDoc('coll/doc4', { 'embedding': vector([5]) }); + + let q = queryWithAddedOrderBy(query('coll'), orderBy('embedding')); + await verifyResults(q, 'coll/arr1', 'coll/doc4', 'coll/doc3', 'coll/map2'); + + q = queryWithAddedFilter( + query('coll'), + filter('embedding', '==', vector([4, 5, 6])) + ); + await verifyResults(q, 'coll/doc3'); + + q = queryWithAddedFilter( + query('coll'), + filter('embedding', '>', vector([4, 5, 6])) + ); + await verifyResults(q, 'coll/map2'); + + q = queryWithAddedFilter( + query('coll'), + filter('embedding', '>=', vector([4])) + ); + await verifyResults(q, 'coll/doc4', 'coll/doc3', 'coll/map2'); + }); + it('support advances queries', async () => { // This test compares local query results with those received from the Java // Server SDK. From cc466c2d6e830089c8f2f5cf21d53feb93f108d9 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 3 May 2024 10:24:18 -0600 Subject: [PATCH 03/15] Missing file. --- packages/firestore/src/model/map_type.ts | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 packages/firestore/src/model/map_type.ts diff --git a/packages/firestore/src/model/map_type.ts b/packages/firestore/src/model/map_type.ts new file mode 100644 index 00000000000..909d95a564f --- /dev/null +++ b/packages/firestore/src/model/map_type.ts @@ -0,0 +1,54 @@ +/*! + * Copyright 2024 Google LLC. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { VectorValue } from '../lite-api/vector_value'; +import { + Value as ProtoValue, + MapValue as ProtoMapValue +} from '../protos/firestore_proto_api'; + +const TYPE_KEY = '__type__'; +const VECTOR_VALUE_SENTINEL = '__vector__'; +export const VECTOR_MAP_VECTORS_KEY = 'value'; + +export function isVectorValue(value: ProtoValue | null): boolean { + const type = (value?.mapValue?.fields || {})[TYPE_KEY]?.stringValue; + return type === VECTOR_VALUE_SENTINEL; +} + +/** + * Creates a new VectorValue proto value (using the internal format). + */ +export function vectorValue(value: VectorValue): ProtoValue { + const mapValue: ProtoMapValue = { + fields: { + [TYPE_KEY]: { + stringValue: VECTOR_VALUE_SENTINEL + }, + [VECTOR_MAP_VECTORS_KEY]: { + arrayValue: { + values: value.toArray().map(value => { + return { + doubleValue: value + }; + }) + } + } + } + }; + + return { mapValue }; +} From a974933f0f865e5d7732c88b0daf5d041d3ea7ca Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 3 May 2024 13:40:10 -0600 Subject: [PATCH 04/15] Tests and fixes --- common/api-review/firestore-lite.api.md | 10 ++++ common/api-review/firestore.api.md | 10 ++++ packages/firestore/lite/index.ts | 5 +- packages/firestore/src/api.ts | 5 +- .../firestore/src/api/field_value_impl.ts | 3 +- .../src/index/firestore_index_value_writer.ts | 7 ++- .../src/lite-api/user_data_reader.ts | 35 ++++++++++-- .../src/lite-api/user_data_writer.ts | 3 +- packages/firestore/src/model/map_type.ts | 54 ------------------- packages/firestore/src/model/values.ts | 31 ++++++++++- .../firestore/test/lite/integration.test.ts | 28 +++++++++- .../test/unit/local/index_manager.test.ts | 4 +- .../test/unit/model/object_value.test.ts | 9 +++- .../firestore/test/unit/model/values.test.ts | 43 +++++++++++++-- .../test/unit/remote/serializer.helper.ts | 30 +++++++++++ 15 files changed, 203 insertions(+), 74 deletions(-) delete mode 100644 packages/firestore/src/model/map_type.ts diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 440fa488c1c..4a9ef4c0171 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -460,6 +460,16 @@ export function updateDoc(refere // @public export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; +// @public +export function vector(values?: number[]): VectorValue; + +// @public +export class VectorValue { + /* Excluded from this release type: __constructor */ + isEqual(other: VectorValue): boolean; + toArray(): number[]; +} + // @public export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryFieldFilterConstraint; diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index f79ef52442e..34b56b97f21 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -745,6 +745,16 @@ export function updateDoc(refere // @public export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; +// @public +export function vector(values?: number[]): VectorValue; + +// @public +export class VectorValue { + /* Excluded from this release type: __constructor */ + isEqual(other: VectorValue): boolean; + toArray(): number[]; +} + // @public export function waitForPendingWrites(firestore: Firestore): Promise; diff --git a/packages/firestore/lite/index.ts b/packages/firestore/lite/index.ts index bec95b76124..f23d3d5e9ec 100644 --- a/packages/firestore/lite/index.ts +++ b/packages/firestore/lite/index.ts @@ -127,7 +127,8 @@ export { arrayRemove, arrayUnion, serverTimestamp, - deleteField + deleteField, + vector } from '../src/lite-api/field_value_impl'; export { @@ -138,6 +139,8 @@ export { snapshotEqual } from '../src/lite-api/snapshot'; +export { VectorValue } from '../src/lite-api/vector_value'; + export { WriteBatch, writeBatch } from '../src/lite-api/write_batch'; export { TransactionOptions } from '../src/lite-api/transaction_options'; diff --git a/packages/firestore/src/api.ts b/packages/firestore/src/api.ts index bcfa6dc5f34..f6ff071dea3 100644 --- a/packages/firestore/src/api.ts +++ b/packages/firestore/src/api.ts @@ -172,9 +172,12 @@ export { arrayUnion, deleteField, increment, - serverTimestamp + serverTimestamp, + vector } from './api/field_value_impl'; +export { VectorValue } from './lite-api/vector_value'; + export { LogLevelString as LogLevel, setLogLevel } from './util/log'; export { Bytes } from './api/bytes'; diff --git a/packages/firestore/src/api/field_value_impl.ts b/packages/firestore/src/api/field_value_impl.ts index 4689352f840..1b1283a3543 100644 --- a/packages/firestore/src/api/field_value_impl.ts +++ b/packages/firestore/src/api/field_value_impl.ts @@ -20,5 +20,6 @@ export { arrayRemove, arrayUnion, serverTimestamp, - deleteField + deleteField, + vector } from '../lite-api/field_value_impl'; diff --git a/packages/firestore/src/index/firestore_index_value_writer.ts b/packages/firestore/src/index/firestore_index_value_writer.ts index 033a30ef78f..dfdb3836578 100644 --- a/packages/firestore/src/index/firestore_index_value_writer.ts +++ b/packages/firestore/src/index/firestore_index_value_writer.ts @@ -16,13 +16,16 @@ */ import { DocumentKey } from '../model/document_key'; -import { isVectorValue, VECTOR_MAP_VECTORS_KEY } from '../model/map_type'; import { normalizeByteString, normalizeNumber, normalizeTimestamp } from '../model/normalize'; -import { isMaxValue } from '../model/values'; +import { + isVectorValue, + VECTOR_MAP_VECTORS_KEY, + isMaxValue +} from '../model/values'; import { ArrayValue, MapValue, Value } from '../protos/firestore_proto_api'; import { fail } from '../util/assert'; import { isNegativeZero } from '../util/types'; diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index 4c5f3578294..e32b2d69fd8 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -26,7 +26,6 @@ import { ParseContext } from '../api/parse_context'; import { DatabaseId } from '../core/database_info'; import { DocumentKey } from '../model/document_key'; import { FieldMask } from '../model/field_mask'; -import { vectorValue } from '../model/map_type'; import { FieldTransform, Mutation, @@ -42,6 +41,11 @@ import { NumericIncrementTransformOperation, ServerTimestampTransform } from '../model/transform_operation'; +import { + TYPE_KEY, + VECTOR_MAP_VECTORS_KEY, + VECTOR_VALUE_SENTINEL +} from '../model/values'; import { newSerializer } from '../platform/serializer'; import { MapValue as ProtoMapValue, @@ -903,9 +907,8 @@ function parseScalarValue( value._key.path ) }; - } - if (value instanceof VectorValue) { - return vectorValue(value); + } else if (value instanceof VectorValue) { + return parseVectorValue(value); } else { throw context.createError( `Unsupported field value: ${valueDescription(value)}` @@ -913,6 +916,30 @@ function parseScalarValue( } } +/** + * Creates a new VectorValue proto value (using the internal format). + */ +export function parseVectorValue(value: VectorValue): ProtoValue { + const mapValue: ProtoMapValue = { + fields: { + [TYPE_KEY]: { + stringValue: VECTOR_VALUE_SENTINEL + }, + [VECTOR_MAP_VECTORS_KEY]: { + arrayValue: { + values: value.toArray().map(value => { + return { + doubleValue: value + }; + }) + } + } + } + }; + + return { mapValue }; +} + /** * Checks whether an object looks like a JSON object that should be converted * into a struct. Normal class/prototype instances are considered to look like diff --git a/packages/firestore/src/lite-api/user_data_writer.ts b/packages/firestore/src/lite-api/user_data_writer.ts index 893fe6f5a60..e903991cb58 100644 --- a/packages/firestore/src/lite-api/user_data_writer.ts +++ b/packages/firestore/src/lite-api/user_data_writer.ts @@ -19,7 +19,6 @@ import { DocumentData } from '@firebase/firestore-types'; import { DatabaseId } from '../core/database_info'; import { DocumentKey } from '../model/document_key'; -import { VECTOR_MAP_VECTORS_KEY } from '../model/map_type'; import { normalizeByteString, normalizeNumber, @@ -31,7 +30,7 @@ import { getPreviousValue } from '../model/server_timestamps'; import { TypeOrder } from '../model/type_order'; -import { typeOrder } from '../model/values'; +import { VECTOR_MAP_VECTORS_KEY, typeOrder } from '../model/values'; import { ApiClientObjectMap, ArrayValue as ProtoArrayValue, diff --git a/packages/firestore/src/model/map_type.ts b/packages/firestore/src/model/map_type.ts deleted file mode 100644 index 909d95a564f..00000000000 --- a/packages/firestore/src/model/map_type.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*! - * Copyright 2024 Google LLC. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { VectorValue } from '../lite-api/vector_value'; -import { - Value as ProtoValue, - MapValue as ProtoMapValue -} from '../protos/firestore_proto_api'; - -const TYPE_KEY = '__type__'; -const VECTOR_VALUE_SENTINEL = '__vector__'; -export const VECTOR_MAP_VECTORS_KEY = 'value'; - -export function isVectorValue(value: ProtoValue | null): boolean { - const type = (value?.mapValue?.fields || {})[TYPE_KEY]?.stringValue; - return type === VECTOR_VALUE_SENTINEL; -} - -/** - * Creates a new VectorValue proto value (using the internal format). - */ -export function vectorValue(value: VectorValue): ProtoValue { - const mapValue: ProtoMapValue = { - fields: { - [TYPE_KEY]: { - stringValue: VECTOR_VALUE_SENTINEL - }, - [VECTOR_MAP_VECTORS_KEY]: { - arrayValue: { - values: value.toArray().map(value => { - return { - doubleValue: value - }; - }) - } - } - } - }; - - return { mapValue }; -} diff --git a/packages/firestore/src/model/values.ts b/packages/firestore/src/model/values.ts index 1d3b6b2c6cc..1977767515e 100644 --- a/packages/firestore/src/model/values.ts +++ b/packages/firestore/src/model/values.ts @@ -21,6 +21,7 @@ import { LatLng, MapValue, Timestamp, + Value as ProtoValue, Value } from '../protos/firestore_proto_api'; import { fail } from '../util/assert'; @@ -29,7 +30,6 @@ import { forEach, objectSize } from '../util/obj'; import { isNegativeZero } from '../util/types'; import { DocumentKey } from './document_key'; -import { isVectorValue, VECTOR_MAP_VECTORS_KEY } from './map_type'; import { normalizeByteString, normalizeNumber, @@ -42,6 +42,7 @@ import { } from './server_timestamps'; import { TypeOrder } from './type_order'; +export const TYPE_KEY = '__type__'; const MAX_VALUE_TYPE = '__max__'; export const MAX_VALUE: Value = { mapValue: { @@ -51,6 +52,9 @@ export const MAX_VALUE: Value = { } }; +export const VECTOR_VALUE_SENTINEL = '__vector__'; +export const VECTOR_MAP_VECTORS_KEY = 'value'; + export const MIN_VALUE: Value = { nullValue: 'NULL_VALUE' }; @@ -615,6 +619,12 @@ export function isMapValue( return !!value && 'mapValue' in value; } +/** Returns true if `value` is a VetorValue. */ +export function isVectorValue(value: ProtoValue | null): boolean { + const type = (value?.mapValue?.fields || {})[TYPE_KEY]?.stringValue; + return type === VECTOR_VALUE_SENTINEL; +} + /** Creates a deep copy of `source`. */ export function deepClone(source: Value): Value { if (source.geoPointValue) { @@ -650,6 +660,17 @@ export function isMaxValue(value: Value): boolean { ); } +export const MIN_VECTOR_VALUE = { + mapValue: { + fields: { + [TYPE_KEY]: { stringValue: VECTOR_VALUE_SENTINEL }, + [VECTOR_MAP_VECTORS_KEY]: { + arrayValue: {} + } + } + } +}; + /** Returns the lowest value for the given value type (inclusive). */ export function valuesGetLowerBound(value: Value): Value { if ('nullValue' in value) { @@ -671,6 +692,9 @@ export function valuesGetLowerBound(value: Value): Value { } else if ('arrayValue' in value) { return { arrayValue: {} }; } else if ('mapValue' in value) { + if (isVectorValue(value)) { + return MIN_VECTOR_VALUE; + } return { mapValue: {} }; } else { return fail('Invalid value type: ' + JSON.stringify(value)); @@ -696,8 +720,11 @@ export function valuesGetUpperBound(value: Value): Value { } else if ('geoPointValue' in value) { return { arrayValue: {} }; } else if ('arrayValue' in value) { - return { mapValue: {} }; + return MIN_VECTOR_VALUE; } else if ('mapValue' in value) { + if (isVectorValue(value)) { + return { mapValue: {} }; + } return MAX_VALUE; } else { return fail('Invalid value type: ' + JSON.stringify(value)); diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 43a47c4e65c..84c70c2680b 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -42,7 +42,8 @@ import { arrayUnion, deleteField, increment, - serverTimestamp + serverTimestamp, + vector } from '../../src/lite-api/field_value_impl'; import { endAt, @@ -2934,3 +2935,28 @@ describe('Aggregate queries - sum / average', () => { } ); }); + +describe('Vectors', () => { + it('can be read and written using the lite SDK', async () => { + return withTestCollection(async coll => { + const ref = await addDoc(coll, { + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]) + }); + await setDoc(ref, { + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]), + vector2: vector([0, 0, 0]) + }); + await updateDoc(ref, { + vector3: vector([-1, -200, -999]) + }); + + const snap1 = await getDoc(ref); + expect(snap1.get('vector0').isEqual(vector([0.0]))).to.be.true; + expect(snap1.get('vector1').isEqual(vector([1, 2, 3.99]))).to.be.true; + expect(snap1.get('vector2').isEqual(vector([0, 0, 0]))).to.be.true; + expect(snap1.get('vector3').isEqual(vector([-1, -200, -999]))).to.be.true; + }); + }); +}); diff --git a/packages/firestore/test/unit/local/index_manager.test.ts b/packages/firestore/test/unit/local/index_manager.test.ts index ee8eba60ff0..2521be99bf5 100644 --- a/packages/firestore/test/unit/local/index_manager.test.ts +++ b/packages/firestore/test/unit/local/index_manager.test.ts @@ -1124,13 +1124,13 @@ describe('IndexedDbIndexManager', async () => { query('coll'), filter('embedding', '>', vector([4, 5, 6])) ); - await verifyResults(q, 'coll/map2'); + await verifyResults(q); q = queryWithAddedFilter( query('coll'), filter('embedding', '>=', vector([4])) ); - await verifyResults(q, 'coll/doc4', 'coll/doc3', 'coll/map2'); + await verifyResults(q, 'coll/doc4', 'coll/doc3'); }); it('support advances queries', async () => { diff --git a/packages/firestore/test/unit/model/object_value.test.ts b/packages/firestore/test/unit/model/object_value.test.ts index d2f3140a191..9e96056d957 100644 --- a/packages/firestore/test/unit/model/object_value.test.ts +++ b/packages/firestore/test/unit/model/object_value.test.ts @@ -17,6 +17,7 @@ import { expect } from 'chai'; +import { vector } from '../../../src/lite-api/field_value_impl'; import { extractFieldMask, ObjectValue } from '../../../src/model/object_value'; import { TypeOrder } from '../../../src/model/type_order'; import { typeOrder } from '../../../src/model/values'; @@ -24,7 +25,10 @@ import { field, mask, wrap, wrapObject } from '../../util/helpers'; describe('ObjectValue', () => { it('can extract fields', () => { - const objValue = wrapObject({ foo: { a: 1, b: true, c: 'string' } }); + const objValue = wrapObject({ + foo: { a: 1, b: true, c: 'string' }, + embedding: vector([1]) + }); expect(typeOrder(objValue.field(field('foo'))!)).to.equal( TypeOrder.ObjectValue @@ -38,6 +42,9 @@ describe('ObjectValue', () => { expect(typeOrder(objValue.field(field('foo.c'))!)).to.equal( TypeOrder.StringValue ); + expect(typeOrder(objValue.field(field('embedding'))!)).to.equal( + TypeOrder.VectorValue + ); expect(objValue.field(field('foo.a.b'))).to.be.null; expect(objValue.field(field('bar'))).to.be.null; diff --git a/packages/firestore/test/unit/model/values.test.ts b/packages/firestore/test/unit/model/values.test.ts index 03dc7fb3cd3..722d2db6fa5 100644 --- a/packages/firestore/test/unit/model/values.test.ts +++ b/packages/firestore/test/unit/model/values.test.ts @@ -19,6 +19,7 @@ import { expect } from 'chai'; import { GeoPoint, Timestamp } from '../../../src'; import { DatabaseId } from '../../../src/core/database_info'; +import { vector } from '../../../src/lite-api/field_value_impl'; import { serverTimestamp } from '../../../src/model/server_timestamps'; import { canonicalId, @@ -28,7 +29,10 @@ import { refValue, deepClone, valuesGetLowerBound, - valuesGetUpperBound + valuesGetUpperBound, + TYPE_KEY, + VECTOR_VALUE_SENTINEL, + VECTOR_MAP_VECTORS_KEY } from '../../../src/model/values'; import * as api from '../../../src/protos/firestore_proto_api'; import { primitiveComparator } from '../../../src/util/misc'; @@ -86,7 +90,9 @@ describe('Values', () => { [wrap({ bar: 1, foo: 2 }), wrap({ foo: 2, bar: 1 })], [wrap({ bar: 2, foo: 1 })], [wrap({ bar: 1, foo: 1 })], - [wrap({ foo: 1 })] + [wrap({ foo: 1 })], + [wrap(vector([]))], + [wrap(vector([1, 2.3, -4.0]))] ]; expectEqualitySets(values, (v1, v2) => valueEquals(v1, v2)); }); @@ -211,6 +217,11 @@ describe('Values', () => { [wrap(['foo', 2])], [wrap(['foo', '0'])], + // vectors + [wrap(vector([100]))], + [wrap(vector([1, 2, 3]))], + [wrap(vector([1, 3, 2]))], + // objects [wrap({ bar: 0 })], [wrap({ bar: 0, foo: 1 })], @@ -316,6 +327,10 @@ describe('Values', () => { { expectedByteSize: 6, elements: [wrap({ a: 'a', b: 'b' }), wrap({ c: 'c', d: 'd' })] + }, + { + expectedByteSize: 49, + elements: [wrap(vector([1, 2])), wrap(vector([-100, 20000098.123445]))] } ]; @@ -344,7 +359,9 @@ describe('Values', () => { [wrap(['a', 'b']), wrap(['a', 'b', 'c'])], [wrap({ a: 'a', b: 'b' }), wrap({ a: 'a', b: 'bc' })], [wrap({ a: 'a', b: 'b' }), wrap({ a: 'a', bc: 'b' })], - [wrap({ a: 'a', b: 'b' }), wrap({ a: 'a', b: 'b', c: 'c' })] + [wrap({ a: 'a', b: 'b' }), wrap({ a: 'a', b: 'b', c: 'c' })], + [wrap({ a: 'a', b: 'b' }), wrap({ a: 'a', b: 'b', c: 'c' })], + [wrap(vector([2, 3])), wrap(vector([1, 2, 3]))] ]; for (const group of relativeGroups) { @@ -398,6 +415,23 @@ describe('Values', () => { [valuesGetLowerBound({ arrayValue: {} }), wrap([])], [wrap([false])], + // vectors + [ + valuesGetLowerBound({ + mapValue: { + fields: { + [TYPE_KEY]: { stringValue: VECTOR_VALUE_SENTINEL }, + [VECTOR_MAP_VECTORS_KEY]: { + arrayValue: { + values: [{ doubleValue: 1 }] + } + } + } + } + }), + wrap(vector([])) + ], + // objects [valuesGetLowerBound({ mapValue: {} }), wrap({})] ]; @@ -486,6 +520,9 @@ describe('Values', () => { }) ) ).to.equal('{a:1,b:2,c:3}'); + expect(canonicalId(wrap(vector([1, 1.0, -2, 3.14])))).to.equal( + '{__type__:__vector__,value:[1,1,-2,3.14]}' + ); expect( canonicalId(wrap({ 'a': ['b', { 'c': new GeoPoint(30, 60) }] })) ).to.equal('{a:[b,{c:geo(30,60)}]}'); diff --git a/packages/firestore/test/unit/remote/serializer.helper.ts b/packages/firestore/test/unit/remote/serializer.helper.ts index afe85796965..d1a234a72e4 100644 --- a/packages/firestore/test/unit/remote/serializer.helper.ts +++ b/packages/firestore/test/unit/remote/serializer.helper.ts @@ -52,6 +52,7 @@ import { } from '../../../src/core/query'; import { SnapshotVersion } from '../../../src/core/snapshot_version'; import { Target, targetEquals, TargetImpl } from '../../../src/core/target'; +import { vector } from '../../../src/lite-api/field_value_impl'; import { parseQueryValue } from '../../../src/lite-api/user_data_reader'; import { TargetData, TargetPurpose } from '../../../src/local/target_data'; import { FieldMask } from '../../../src/model/field_mask'; @@ -535,6 +536,35 @@ export function serializerTest( 'projects/test-project/databases/(default)/documents/docs/1' }); }); + + it('converts VectorValue', () => { + const original = vector([1, 2, 3]); + const objValue = wrap(original); + expect(userDataWriter.convertValue(objValue)).to.deep.equal(original); + + const expectedJson: api.Value = { + mapValue: { + fields: { + '__type__': { stringValue: '__vector__' }, + value: { + arrayValue: { + values: [ + { doubleValue: 1 }, + { doubleValue: 2 }, + { doubleValue: 3 } + ] + } + } + } + } + }; + + verifyFieldValueRoundTrip({ + value: original, + valueType: 'mapValue', + jsonValue: expectedJson.mapValue + }); + }); }); describe('toKey', () => { From 3f719238a3deadb2961e79ed7218d2d1e5d2542d Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 3 May 2024 13:55:20 -0600 Subject: [PATCH 05/15] Missing header. --- packages/firestore/src/lite-api/vector_value.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/firestore/src/lite-api/vector_value.ts b/packages/firestore/src/lite-api/vector_value.ts index 7f6d36042c0..5f065d1ce53 100644 --- a/packages/firestore/src/lite-api/vector_value.ts +++ b/packages/firestore/src/lite-api/vector_value.ts @@ -1,3 +1,20 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { isPrimitiveArrayEqual } from '../util/array'; /** From c140c1399d9c88784122255d967f681b43077d54 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 3 May 2024 14:33:35 -0600 Subject: [PATCH 06/15] Update import --- packages/firestore/test/integration/api/database.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 2d802265cad..86434594045 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -20,7 +20,6 @@ import { Deferred } from '@firebase/util'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import { vector } from '../../../src/lite-api/field_value_impl'; import { EventsAccumulator } from '../util/events_accumulator'; import { addDoc, @@ -65,7 +64,8 @@ import { newTestFirestore, SnapshotOptions, newTestApp, - QuerySnapshot + QuerySnapshot, + vector } from '../util/firebase_export'; import { apiDescribe, From 2a365b8f15f4f58011d26c6952374a58e7dec376 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 3 May 2024 14:36:53 -0600 Subject: [PATCH 07/15] Remove MemoryLruPersistenceMode from persistence modes, reverting a change unrelated to this PR. --- packages/firestore/test/integration/util/helpers.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index bdfc7e7daa5..647360db463 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -182,8 +182,7 @@ function apiDescribeInternal( testSuite: (persistence: PersistenceMode) => void ): void { const persistenceModes: PersistenceMode[] = [ - new MemoryEagerPersistenceMode(), - new MemoryLruPersistenceMode() + new MemoryEagerPersistenceMode() ]; if (isPersistenceAvailable()) { persistenceModes.push(new IndexedDbPersistenceMode()); From d5449f5db71a7d90bee5638203bc2e38f121c92f Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Mon, 6 May 2024 09:32:21 -0600 Subject: [PATCH 08/15] Fixed issue with Inf in a vector when using Proto3JSON. --- .../src/lite-api/user_data_reader.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index e32b2d69fd8..f9c8ff6f223 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -51,7 +51,7 @@ import { MapValue as ProtoMapValue, Value as ProtoValue } from '../protos/firestore_proto_api'; -import { toNumber } from '../remote/number_serializer'; +import { toDouble, toNumber } from '../remote/number_serializer'; import { JsonProtoSerializer, toBytes, @@ -908,7 +908,7 @@ function parseScalarValue( ) }; } else if (value instanceof VectorValue) { - return parseVectorValue(value); + return parseVectorValue(value, context); } else { throw context.createError( `Unsupported field value: ${valueDescription(value)}` @@ -919,7 +919,10 @@ function parseScalarValue( /** * Creates a new VectorValue proto value (using the internal format). */ -export function parseVectorValue(value: VectorValue): ProtoValue { +export function parseVectorValue( + value: VectorValue, + context: ParseContextImpl +): ProtoValue { const mapValue: ProtoMapValue = { fields: { [TYPE_KEY]: { @@ -928,9 +931,13 @@ export function parseVectorValue(value: VectorValue): ProtoValue { [VECTOR_MAP_VECTORS_KEY]: { arrayValue: { values: value.toArray().map(value => { - return { - doubleValue: value - }; + if (typeof value !== 'number') { + throw context.createError( + 'VectorValues must only contain numeric values.' + ); + } + + return toDouble(context.serializer, value); }) } } From 8c74eff79db1313a551c5f1cffd8509b90609281 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Mon, 6 May 2024 09:40:20 -0600 Subject: [PATCH 09/15] PR feedback. --- .../firestore/test/integration/api/database.test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 86434594045..cad62afd59b 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -65,7 +65,8 @@ import { SnapshotOptions, newTestApp, QuerySnapshot, - vector + vector, + getDocsFromServer } from '../util/firebase_export'; import { apiDescribe, @@ -710,9 +711,9 @@ apiDescribe('Database', persistence => { return withTestCollection(persistence, docs, async randomCol => { // We validate that the SDK orders the vector field the same way as the backend - // by comparing the sort order of vector fields from a Query.get() and - // Query.onSnapshot(). Query.onSnapshot() will return sort order of the SDK, - // and Query.get() will return sort order of the backend. + // by comparing the sort order of vector fields from getDocsFromServer and + // onSnapshot. onSnapshot will return sort order of the SDK, + // and getDocsFromServer will return sort order of the backend. const orderedQuery = query(randomCol, orderBy('embedding')); const gotInitialSnapshot = new Deferred(); @@ -730,7 +731,7 @@ apiDescribe('Database', persistence => { const watchSnapshot = await gotInitialSnapshot.promise; unsubscribe(); - const getSnapshot = await getDocs(orderedQuery); + const getSnapshot = await getDocsFromServer(orderedQuery); // Compare the snapshot (including sort order) of a snapshot // from Query.onSnapshot() to an actual snapshot from Query.get() From 1221a9262f1dcc53c4ea1dda8dd0c2c6ff1c7098 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:57:32 -0600 Subject: [PATCH 10/15] docs-devsite --- docs-devsite/_toc.yaml | 555 --------------------------------- docs-devsite/firestore_.md | 27 ++ docs-devsite/firestore_lite.md | 27 ++ 3 files changed, 54 insertions(+), 555 deletions(-) delete mode 100644 docs-devsite/_toc.yaml diff --git a/docs-devsite/_toc.yaml b/docs-devsite/_toc.yaml deleted file mode 100644 index 7412d572013..00000000000 --- a/docs-devsite/_toc.yaml +++ /dev/null @@ -1,555 +0,0 @@ -toc: - - title: firebase - path: /docs/reference/js/index - - title: analytics - path: /docs/reference/js/analytics.md - section: - - title: Analytics - path: /docs/reference/js/analytics.analytics.md - - title: AnalyticsCallOptions - path: /docs/reference/js/analytics.analyticscalloptions.md - - title: AnalyticsSettings - path: /docs/reference/js/analytics.analyticssettings.md - - title: ConsentSettings - path: /docs/reference/js/analytics.consentsettings.md - - title: ControlParams - path: /docs/reference/js/analytics.controlparams.md - - title: CustomParams - path: /docs/reference/js/analytics.customparams.md - - title: EventParams - path: /docs/reference/js/analytics.eventparams.md - - title: GtagConfigParams - path: /docs/reference/js/analytics.gtagconfigparams.md - - title: Item - path: /docs/reference/js/analytics.item.md - - title: Promotion - path: /docs/reference/js/analytics.promotion.md - - title: SettingsOptions - path: /docs/reference/js/analytics.settingsoptions.md - - title: app - path: /docs/reference/js/app.md - section: - - title: FirebaseApp - path: /docs/reference/js/app.firebaseapp.md - - title: FirebaseAppSettings - path: /docs/reference/js/app.firebaseappsettings.md - - title: FirebaseOptions - path: /docs/reference/js/app.firebaseoptions.md - - title: FirebaseServerApp - path: /docs/reference/js/app.firebaseserverapp.md - - title: FirebaseServerAppSettings - path: /docs/reference/js/app.firebaseserverappsettings.md - - title: app-check - path: /docs/reference/js/app-check.md - section: - - title: AppCheck - path: /docs/reference/js/app-check.appcheck.md - - title: AppCheckOptions - path: /docs/reference/js/app-check.appcheckoptions.md - - title: AppCheckToken - path: /docs/reference/js/app-check.appchecktoken.md - - title: AppCheckTokenResult - path: /docs/reference/js/app-check.appchecktokenresult.md - - title: CustomProvider - path: /docs/reference/js/app-check.customprovider.md - - title: CustomProviderOptions - path: /docs/reference/js/app-check.customprovideroptions.md - - title: ReCaptchaEnterpriseProvider - path: /docs/reference/js/app-check.recaptchaenterpriseprovider.md - - title: ReCaptchaV3Provider - path: /docs/reference/js/app-check.recaptchav3provider.md - - title: auth - path: /docs/reference/js/auth.md - section: - - title: ActionCodeInfo - path: /docs/reference/js/auth.actioncodeinfo.md - - title: ActionCodeSettings - path: /docs/reference/js/auth.actioncodesettings.md - - title: ActionCodeURL - path: /docs/reference/js/auth.actioncodeurl.md - - title: AdditionalUserInfo - path: /docs/reference/js/auth.additionaluserinfo.md - - title: ApplicationVerifier - path: /docs/reference/js/auth.applicationverifier.md - - title: Auth - path: /docs/reference/js/auth.auth.md - - title: AuthCredential - path: /docs/reference/js/auth.authcredential.md - - title: AuthError - path: /docs/reference/js/auth.autherror.md - - title: AuthErrorMap - path: /docs/reference/js/auth.autherrormap.md - - title: AuthProvider - path: /docs/reference/js/auth.authprovider.md - - title: AuthSettings - path: /docs/reference/js/auth.authsettings.md - - title: Config - path: /docs/reference/js/auth.config.md - - title: ConfirmationResult - path: /docs/reference/js/auth.confirmationresult.md - - title: Dependencies - path: /docs/reference/js/auth.dependencies.md - - title: EmailAuthCredential - path: /docs/reference/js/auth.emailauthcredential.md - - title: EmailAuthProvider - path: /docs/reference/js/auth.emailauthprovider.md - - title: EmulatorConfig - path: /docs/reference/js/auth.emulatorconfig.md - - title: FacebookAuthProvider - path: /docs/reference/js/auth.facebookauthprovider.md - - title: GithubAuthProvider - path: /docs/reference/js/auth.githubauthprovider.md - - title: GoogleAuthProvider - path: /docs/reference/js/auth.googleauthprovider.md - - title: IdTokenResult - path: /docs/reference/js/auth.idtokenresult.md - - title: MultiFactorAssertion - path: /docs/reference/js/auth.multifactorassertion.md - - title: MultiFactorError - path: /docs/reference/js/auth.multifactorerror.md - - title: MultiFactorInfo - path: /docs/reference/js/auth.multifactorinfo.md - - title: MultiFactorResolver - path: /docs/reference/js/auth.multifactorresolver.md - - title: MultiFactorSession - path: /docs/reference/js/auth.multifactorsession.md - - title: MultiFactorUser - path: /docs/reference/js/auth.multifactoruser.md - - title: OAuthCredential - path: /docs/reference/js/auth.oauthcredential.md - - title: OAuthCredentialOptions - path: /docs/reference/js/auth.oauthcredentialoptions.md - - title: OAuthProvider - path: /docs/reference/js/auth.oauthprovider.md - - title: ParsedToken - path: /docs/reference/js/auth.parsedtoken.md - - title: PasswordPolicy - path: /docs/reference/js/auth.passwordpolicy.md - - title: PasswordValidationStatus - path: /docs/reference/js/auth.passwordvalidationstatus.md - - title: Persistence - path: /docs/reference/js/auth.persistence.md - - title: PhoneAuthCredential - path: /docs/reference/js/auth.phoneauthcredential.md - - title: PhoneAuthProvider - path: /docs/reference/js/auth.phoneauthprovider.md - - title: PhoneMultiFactorAssertion - path: /docs/reference/js/auth.phonemultifactorassertion.md - - title: PhoneMultiFactorEnrollInfoOptions - path: /docs/reference/js/auth.phonemultifactorenrollinfooptions.md - - title: PhoneMultiFactorGenerator - path: /docs/reference/js/auth.phonemultifactorgenerator.md - - title: PhoneMultiFactorInfo - path: /docs/reference/js/auth.phonemultifactorinfo.md - - title: PhoneMultiFactorSignInInfoOptions - path: /docs/reference/js/auth.phonemultifactorsignininfooptions.md - - title: PhoneSingleFactorInfoOptions - path: /docs/reference/js/auth.phonesinglefactorinfooptions.md - - title: PopupRedirectResolver - path: /docs/reference/js/auth.popupredirectresolver.md - - title: ReactNativeAsyncStorage - path: /docs/reference/js/auth.reactnativeasyncstorage.md - - title: RecaptchaParameters - path: /docs/reference/js/auth.recaptchaparameters.md - - title: RecaptchaVerifier - path: /docs/reference/js/auth.recaptchaverifier.md - - title: SAMLAuthProvider - path: /docs/reference/js/auth.samlauthprovider.md - - title: TotpMultiFactorAssertion - path: /docs/reference/js/auth.totpmultifactorassertion.md - - title: TotpMultiFactorGenerator - path: /docs/reference/js/auth.totpmultifactorgenerator.md - - title: TotpMultiFactorInfo - path: /docs/reference/js/auth.totpmultifactorinfo.md - - title: TotpSecret - path: /docs/reference/js/auth.totpsecret.md - - title: TwitterAuthProvider - path: /docs/reference/js/auth.twitterauthprovider.md - - title: User - path: /docs/reference/js/auth.user.md - - title: UserCredential - path: /docs/reference/js/auth.usercredential.md - - title: UserInfo - path: /docs/reference/js/auth.userinfo.md - - title: UserMetadata - path: /docs/reference/js/auth.usermetadata.md - - title: database - path: /docs/reference/js/database.md - section: - - title: Database - path: /docs/reference/js/database.database.md - - title: DatabaseReference - path: /docs/reference/js/database.databasereference.md - - title: DataSnapshot - path: /docs/reference/js/database.datasnapshot.md - - title: IteratedDataSnapshot - path: /docs/reference/js/database.iterateddatasnapshot.md - - title: ListenOptions - path: /docs/reference/js/database.listenoptions.md - - title: OnDisconnect - path: /docs/reference/js/database.ondisconnect.md - - title: Query - path: /docs/reference/js/database.query.md - - title: QueryConstraint - path: /docs/reference/js/database.queryconstraint.md - - title: ThenableReference - path: /docs/reference/js/database.thenablereference.md - - title: TransactionOptions - path: /docs/reference/js/database.transactionoptions.md - - title: TransactionResult - path: /docs/reference/js/database.transactionresult.md - - title: firestore - path: /docs/reference/js/firestore_.md - section: - - title: AggregateField - path: /docs/reference/js/firestore_.aggregatefield.md - - title: AggregateQuerySnapshot - path: /docs/reference/js/firestore_.aggregatequerysnapshot.md - - title: AggregateSpec - path: /docs/reference/js/firestore_.aggregatespec.md - - title: Bytes - path: /docs/reference/js/firestore_.bytes.md - - title: CollectionReference - path: /docs/reference/js/firestore_.collectionreference.md - - title: DocumentChange - path: /docs/reference/js/firestore_.documentchange.md - - title: DocumentData - path: /docs/reference/js/firestore_.documentdata.md - - title: DocumentReference - path: /docs/reference/js/firestore_.documentreference.md - - title: DocumentSnapshot - path: /docs/reference/js/firestore_.documentsnapshot.md - - title: ExperimentalLongPollingOptions - path: /docs/reference/js/firestore_.experimentallongpollingoptions.md - - title: FieldPath - path: /docs/reference/js/firestore_.fieldpath.md - - title: FieldValue - path: /docs/reference/js/firestore_.fieldvalue.md - - title: Firestore - path: /docs/reference/js/firestore_.firestore.md - - title: FirestoreDataConverter - path: /docs/reference/js/firestore_.firestoredataconverter.md - - title: FirestoreError - path: /docs/reference/js/firestore_.firestoreerror.md - - title: FirestoreSettings - path: /docs/reference/js/firestore_.firestoresettings.md - - title: GeoPoint - path: /docs/reference/js/firestore_.geopoint.md - - title: Index - path: /docs/reference/js/firestore_.index.md - - title: IndexConfiguration - path: /docs/reference/js/firestore_.indexconfiguration.md - - title: IndexField - path: /docs/reference/js/firestore_.indexfield.md - - title: LoadBundleTask - path: /docs/reference/js/firestore_.loadbundletask.md - - title: LoadBundleTaskProgress - path: /docs/reference/js/firestore_.loadbundletaskprogress.md - - title: MemoryCacheSettings - path: /docs/reference/js/firestore_.memorycachesettings.md - - title: MemoryEagerGarbageCollector - path: /docs/reference/js/firestore_.memoryeagergarbagecollector.md - - title: MemoryLocalCache - path: /docs/reference/js/firestore_.memorylocalcache.md - - title: MemoryLruGarbageCollector - path: /docs/reference/js/firestore_.memorylrugarbagecollector.md - - title: PersistenceSettings - path: /docs/reference/js/firestore_.persistencesettings.md - - title: PersistentCacheIndexManager - path: /docs/reference/js/firestore_.persistentcacheindexmanager.md - - title: PersistentCacheSettings - path: /docs/reference/js/firestore_.persistentcachesettings.md - - title: PersistentLocalCache - path: /docs/reference/js/firestore_.persistentlocalcache.md - - title: PersistentMultipleTabManager - path: /docs/reference/js/firestore_.persistentmultipletabmanager.md - - title: PersistentSingleTabManager - path: /docs/reference/js/firestore_.persistentsingletabmanager.md - - title: PersistentSingleTabManagerSettings - path: /docs/reference/js/firestore_.persistentsingletabmanagersettings.md - - title: Query - path: /docs/reference/js/firestore_.query.md - - title: QueryCompositeFilterConstraint - path: /docs/reference/js/firestore_.querycompositefilterconstraint.md - - title: QueryConstraint - path: /docs/reference/js/firestore_.queryconstraint.md - - title: QueryDocumentSnapshot - path: /docs/reference/js/firestore_.querydocumentsnapshot.md - - title: QueryEndAtConstraint - path: /docs/reference/js/firestore_.queryendatconstraint.md - - title: QueryFieldFilterConstraint - path: /docs/reference/js/firestore_.queryfieldfilterconstraint.md - - title: QueryLimitConstraint - path: /docs/reference/js/firestore_.querylimitconstraint.md - - title: QueryOrderByConstraint - path: /docs/reference/js/firestore_.queryorderbyconstraint.md - - title: QuerySnapshot - path: /docs/reference/js/firestore_.querysnapshot.md - - title: QueryStartAtConstraint - path: /docs/reference/js/firestore_.querystartatconstraint.md - - title: SnapshotListenOptions - path: /docs/reference/js/firestore_.snapshotlistenoptions.md - - title: SnapshotMetadata - path: /docs/reference/js/firestore_.snapshotmetadata.md - - title: SnapshotOptions - path: /docs/reference/js/firestore_.snapshotoptions.md - - title: Timestamp - path: /docs/reference/js/firestore_.timestamp.md - - title: Transaction - path: /docs/reference/js/firestore_.transaction.md - - title: TransactionOptions - path: /docs/reference/js/firestore_.transactionoptions.md - - title: Unsubscribe - path: /docs/reference/js/firestore_.unsubscribe.md - - title: WriteBatch - path: /docs/reference/js/firestore_.writebatch.md - - title: firestore/lite - path: /docs/reference/js/firestore_lite.md - section: - - title: AggregateField - path: /docs/reference/js/firestore_lite.aggregatefield.md - - title: AggregateQuerySnapshot - path: /docs/reference/js/firestore_lite.aggregatequerysnapshot.md - - title: AggregateSpec - path: /docs/reference/js/firestore_lite.aggregatespec.md - - title: Bytes - path: /docs/reference/js/firestore_lite.bytes.md - - title: CollectionReference - path: /docs/reference/js/firestore_lite.collectionreference.md - - title: DocumentData - path: /docs/reference/js/firestore_lite.documentdata.md - - title: DocumentReference - path: /docs/reference/js/firestore_lite.documentreference.md - - title: DocumentSnapshot - path: /docs/reference/js/firestore_lite.documentsnapshot.md - - title: FieldPath - path: /docs/reference/js/firestore_lite.fieldpath.md - - title: FieldValue - path: /docs/reference/js/firestore_lite.fieldvalue.md - - title: Firestore - path: /docs/reference/js/firestore_lite.firestore.md - - title: FirestoreDataConverter - path: /docs/reference/js/firestore_lite.firestoredataconverter.md - - title: FirestoreError - path: /docs/reference/js/firestore_lite.firestoreerror.md - - title: GeoPoint - path: /docs/reference/js/firestore_lite.geopoint.md - - title: Query - path: /docs/reference/js/firestore_lite.query.md - - title: QueryCompositeFilterConstraint - path: /docs/reference/js/firestore_lite.querycompositefilterconstraint.md - - title: QueryConstraint - path: /docs/reference/js/firestore_lite.queryconstraint.md - - title: QueryDocumentSnapshot - path: /docs/reference/js/firestore_lite.querydocumentsnapshot.md - - title: QueryEndAtConstraint - path: /docs/reference/js/firestore_lite.queryendatconstraint.md - - title: QueryFieldFilterConstraint - path: /docs/reference/js/firestore_lite.queryfieldfilterconstraint.md - - title: QueryLimitConstraint - path: /docs/reference/js/firestore_lite.querylimitconstraint.md - - title: QueryOrderByConstraint - path: /docs/reference/js/firestore_lite.queryorderbyconstraint.md - - title: QuerySnapshot - path: /docs/reference/js/firestore_lite.querysnapshot.md - - title: QueryStartAtConstraint - path: /docs/reference/js/firestore_lite.querystartatconstraint.md - - title: Settings - path: /docs/reference/js/firestore_lite.settings.md - - title: Timestamp - path: /docs/reference/js/firestore_lite.timestamp.md - - title: Transaction - path: /docs/reference/js/firestore_lite.transaction.md - - title: TransactionOptions - path: /docs/reference/js/firestore_lite.transactionoptions.md - - title: WriteBatch - path: /docs/reference/js/firestore_lite.writebatch.md - - title: functions - path: /docs/reference/js/functions.md - section: - - title: Functions - path: /docs/reference/js/functions.functions.md - - title: FunctionsError - path: /docs/reference/js/functions.functionserror.md - - title: HttpsCallableOptions - path: /docs/reference/js/functions.httpscallableoptions.md - - title: HttpsCallableResult - path: /docs/reference/js/functions.httpscallableresult.md - - title: installations - path: /docs/reference/js/installations.md - section: - - title: Installations - path: /docs/reference/js/installations.installations.md - - title: messaging - path: /docs/reference/js/messaging_.md - section: - - title: FcmOptions - path: /docs/reference/js/messaging_.fcmoptions.md - - title: GetTokenOptions - path: /docs/reference/js/messaging_.gettokenoptions.md - - title: MessagePayload - path: /docs/reference/js/messaging_.messagepayload.md - - title: Messaging - path: /docs/reference/js/messaging_.messaging.md - - title: NotificationPayload - path: /docs/reference/js/messaging_.notificationpayload.md - - title: messaging/sw - path: /docs/reference/js/messaging_sw.md - section: - - title: FcmOptions - path: /docs/reference/js/messaging_sw.fcmoptions.md - - title: GetTokenOptions - path: /docs/reference/js/messaging_sw.gettokenoptions.md - - title: MessagePayload - path: /docs/reference/js/messaging_sw.messagepayload.md - - title: Messaging - path: /docs/reference/js/messaging_sw.messaging.md - - title: NotificationPayload - path: /docs/reference/js/messaging_sw.notificationpayload.md - - title: performance - path: /docs/reference/js/performance.md - section: - - title: FirebasePerformance - path: /docs/reference/js/performance.firebaseperformance.md - - title: PerformanceSettings - path: /docs/reference/js/performance.performancesettings.md - - title: PerformanceTrace - path: /docs/reference/js/performance.performancetrace.md - - title: remote-config - path: /docs/reference/js/remote-config.md - section: - - title: RemoteConfig - path: /docs/reference/js/remote-config.remoteconfig.md - - title: RemoteConfigSettings - path: /docs/reference/js/remote-config.remoteconfigsettings.md - - title: Value - path: /docs/reference/js/remote-config.value.md - - title: storage - path: /docs/reference/js/storage.md - section: - - title: FirebaseStorage - path: /docs/reference/js/storage.firebasestorage.md - - title: FullMetadata - path: /docs/reference/js/storage.fullmetadata.md - - title: ListOptions - path: /docs/reference/js/storage.listoptions.md - - title: ListResult - path: /docs/reference/js/storage.listresult.md - - title: SettableMetadata - path: /docs/reference/js/storage.settablemetadata.md - - title: StorageError - path: /docs/reference/js/storage.storageerror.md - - title: StorageObserver - path: /docs/reference/js/storage.storageobserver.md - - title: StorageReference - path: /docs/reference/js/storage.storagereference.md - - title: UploadMetadata - path: /docs/reference/js/storage.uploadmetadata.md - - title: UploadResult - path: /docs/reference/js/storage.uploadresult.md - - title: UploadTask - path: /docs/reference/js/storage.uploadtask.md - - title: UploadTaskSnapshot - path: /docs/reference/js/storage.uploadtasksnapshot.md - - title: vertexai-preview - path: /docs/reference/js/vertexai-preview.md - section: - - title: BaseParams - path: /docs/reference/js/vertexai-preview.baseparams.md - - title: ChatSession - path: /docs/reference/js/vertexai-preview.chatsession.md - - title: Citation - path: /docs/reference/js/vertexai-preview.citation.md - - title: CitationMetadata - path: /docs/reference/js/vertexai-preview.citationmetadata.md - - title: Content - path: /docs/reference/js/vertexai-preview.content.md - - title: CountTokensRequest - path: /docs/reference/js/vertexai-preview.counttokensrequest.md - - title: CountTokensResponse - path: /docs/reference/js/vertexai-preview.counttokensresponse.md - - title: CustomErrorData - path: /docs/reference/js/vertexai-preview.customerrordata.md - - title: Date_2 - path: /docs/reference/js/vertexai-preview.date_2.md - - title: EnhancedGenerateContentResponse - path: /docs/reference/js/vertexai-preview.enhancedgeneratecontentresponse.md - - title: ErrorDetails - path: /docs/reference/js/vertexai-preview.errordetails.md - - title: FileData - path: /docs/reference/js/vertexai-preview.filedata.md - - title: FileDataPart - path: /docs/reference/js/vertexai-preview.filedatapart.md - - title: FunctionCall - path: /docs/reference/js/vertexai-preview.functioncall.md - - title: FunctionCallingConfig - path: /docs/reference/js/vertexai-preview.functioncallingconfig.md - - title: FunctionCallPart - path: /docs/reference/js/vertexai-preview.functioncallpart.md - - title: FunctionDeclaration - path: /docs/reference/js/vertexai-preview.functiondeclaration.md - - title: FunctionDeclarationSchema - path: /docs/reference/js/vertexai-preview.functiondeclarationschema.md - - title: FunctionDeclarationSchemaProperty - path: >- - /docs/reference/js/vertexai-preview.functiondeclarationschemaproperty.md - - title: FunctionDeclarationsTool - path: /docs/reference/js/vertexai-preview.functiondeclarationstool.md - - title: FunctionResponse - path: /docs/reference/js/vertexai-preview.functionresponse.md - - title: FunctionResponsePart - path: /docs/reference/js/vertexai-preview.functionresponsepart.md - - title: GenerateContentCandidate - path: /docs/reference/js/vertexai-preview.generatecontentcandidate.md - - title: GenerateContentRequest - path: /docs/reference/js/vertexai-preview.generatecontentrequest.md - - title: GenerateContentResponse - path: /docs/reference/js/vertexai-preview.generatecontentresponse.md - - title: GenerateContentResult - path: /docs/reference/js/vertexai-preview.generatecontentresult.md - - title: GenerateContentStreamResult - path: /docs/reference/js/vertexai-preview.generatecontentstreamresult.md - - title: GenerationConfig - path: /docs/reference/js/vertexai-preview.generationconfig.md - - title: GenerativeContentBlob - path: /docs/reference/js/vertexai-preview.generativecontentblob.md - - title: GenerativeModel - path: /docs/reference/js/vertexai-preview.generativemodel.md - - title: GroundingAttribution - path: /docs/reference/js/vertexai-preview.groundingattribution.md - - title: GroundingMetadata - path: /docs/reference/js/vertexai-preview.groundingmetadata.md - - title: InlineDataPart - path: /docs/reference/js/vertexai-preview.inlinedatapart.md - - title: ModelParams - path: /docs/reference/js/vertexai-preview.modelparams.md - - title: PromptFeedback - path: /docs/reference/js/vertexai-preview.promptfeedback.md - - title: RequestOptions - path: /docs/reference/js/vertexai-preview.requestoptions.md - - title: RetrievedContextAttribution - path: /docs/reference/js/vertexai-preview.retrievedcontextattribution.md - - title: SafetyRating - path: /docs/reference/js/vertexai-preview.safetyrating.md - - title: SafetySetting - path: /docs/reference/js/vertexai-preview.safetysetting.md - - title: Segment - path: /docs/reference/js/vertexai-preview.segment.md - - title: StartChatParams - path: /docs/reference/js/vertexai-preview.startchatparams.md - - title: TextPart - path: /docs/reference/js/vertexai-preview.textpart.md - - title: ToolConfig - path: /docs/reference/js/vertexai-preview.toolconfig.md - - title: UsageMetadata - path: /docs/reference/js/vertexai-preview.usagemetadata.md - - title: VertexAI - path: /docs/reference/js/vertexai-preview.vertexai.md - - title: VertexAIError - path: /docs/reference/js/vertexai-preview.vertexaierror.md - - title: VertexAIOptions - path: /docs/reference/js/vertexai-preview.vertexaioptions.md - - title: VideoMetadata - path: /docs/reference/js/vertexai-preview.videometadata.md - - title: WebAttribution - path: /docs/reference/js/vertexai-preview.webattribution.md diff --git a/docs-devsite/firestore_.md b/docs-devsite/firestore_.md index 145793a3f04..1f18e22733e 100644 --- a/docs-devsite/firestore_.md +++ b/docs-devsite/firestore_.md @@ -124,6 +124,8 @@ https://github.com/firebase/firebase-js-sdk | [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. | | [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. | | [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 orderBy of this query. | +| function(values, ...) | +| [vector(values)](./firestore_.md#vector_0dbdaf2) | Creates a new VectorValue constructed with a copy of the given array of numbers. | ## Classes @@ -155,6 +157,7 @@ https://github.com/firebase/firebase-js-sdk | [SnapshotMetadata](./firestore_.snapshotmetadata.md#snapshotmetadata_class) | Metadata about a snapshot, describing the state of the snapshot. | | [Timestamp](./firestore_.timestamp.md#timestamp_class) | A Timestamp 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). | | [Transaction](./firestore_.transaction.md#transaction_class) | A reference to a transaction.The Transaction object passed to a transaction's updateFunction provides the methods to read and write data within the transaction context. See [runTransaction()](./firestore_.md#runtransaction_6f03ec4). | +| [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | Represent a vector type in Firestore documents. Create an instance with . VectorValue | | [WriteBatch](./firestore_.writebatch.md#writebatch_class) | A write batch, used to perform multiple writes as a single atomic unit.A WriteBatch 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. | ## Interfaces @@ -2452,6 +2455,30 @@ export declare function startAt( A [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) to pass to `query()`. +## function(values, ...) + +### vector(values) {:#vector_0dbdaf2} + +Creates a new `VectorValue` constructed with a copy of the given array of numbers. + +Signature: + +```typescript +export declare function vector(values?: number[]): VectorValue; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| values | number\[\] | Create a VectorValue instance with a copy of this array of numbers. | + +Returns: + +[VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) + +A new `VectorValue` constructed with a copy of the given array of numbers. + ## CACHE\_SIZE\_UNLIMITED 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. diff --git a/docs-devsite/firestore_lite.md b/docs-devsite/firestore_lite.md index 17ef56b501c..fec41a15a70 100644 --- a/docs-devsite/firestore_lite.md +++ b/docs-devsite/firestore_lite.md @@ -89,6 +89,8 @@ https://github.com/firebase/firebase-js-sdk | [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. | | [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. | | [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 orderBy of this query. | +| function(values, ...) | +| [vector(values)](./firestore_lite.md#vector_0dbdaf2) | Creates a new VectorValue constructed with a copy of the given array of numbers. | ## Classes @@ -117,6 +119,7 @@ https://github.com/firebase/firebase-js-sdk | [QueryStartAtConstraint](./firestore_lite.querystartatconstraint.md#querystartatconstraint_class) | A QueryStartAtConstraint is used to exclude documents from the start of a result set returned by a Firestore query. QueryStartAtConstraints 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 QueryStartAtConstraint. | | [Timestamp](./firestore_lite.timestamp.md#timestamp_class) | A Timestamp 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). | | [Transaction](./firestore_lite.transaction.md#transaction_class) | A reference to a transaction.The Transaction object passed to a transaction's updateFunction provides the methods to read and write data within the transaction context. See [runTransaction()](./firestore_.md#runtransaction_6f03ec4). | +| [VectorValue](./firestore_lite.vectorvalue.md#vectorvalue_class) | Represent a vector type in Firestore documents. Create an instance with . VectorValue | | [WriteBatch](./firestore_lite.writebatch.md#writebatch_class) | A write batch, used to perform multiple writes as a single atomic unit.A WriteBatch 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. | ## Interfaces @@ -1568,6 +1571,30 @@ export declare function startAt( A [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) to pass to `query()`. +## function(values, ...) + +### vector(values) {:#vector_0dbdaf2} + +Creates a new `VectorValue` constructed with a copy of the given array of numbers. + +Signature: + +```typescript +export declare function vector(values?: number[]): VectorValue; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| values | number\[\] | Create a VectorValue instance with a copy of this array of numbers. | + +Returns: + +[VectorValue](./firestore_lite.vectorvalue.md#vectorvalue_class) + +A new `VectorValue` constructed with a copy of the given array of numbers. + ## AddPrefixToKeys Returns a new map where every key is prefixed with the outer key appended to a dot. From d242a197cd277c819f77cfe78a376f77057a012b Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:03:26 -0600 Subject: [PATCH 11/15] lint --- packages/firestore/test/integration/api/database.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index d4be54f7ccd..eda3978dd46 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -64,8 +64,7 @@ import { newTestFirestore, SnapshotOptions, newTestApp, - FirestoreError - newTestApp, + FirestoreError, QuerySnapshot, vector, getDocsFromServer From 9dafcda6db4165f498380be400e859ae9932969d Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:29:38 -0600 Subject: [PATCH 12/15] Update Firestore emulator version used for testing --- scripts/emulator-testing/emulators/firestore-emulator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/emulator-testing/emulators/firestore-emulator.ts b/scripts/emulator-testing/emulators/firestore-emulator.ts index 57186af0f64..c8cdf811c02 100644 --- a/scripts/emulator-testing/emulators/firestore-emulator.ts +++ b/scripts/emulator-testing/emulators/firestore-emulator.ts @@ -26,7 +26,7 @@ export class FirestoreEmulator extends Emulator { // Use locked version of emulator for test to be deterministic. // The latest version can be found from firestore emulator doc: // https://firebase.google.com/docs/firestore/security/test-rules-emulator - 'https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.18.2.jar', + 'https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.19.7.jar', port ); this.projectId = projectId; From 83a81a519267967c1b137b901ce09f716200d548 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:40:25 -0600 Subject: [PATCH 13/15] Create nice-eyes-tan.md --- .changeset/nice-eyes-tan.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nice-eyes-tan.md diff --git a/.changeset/nice-eyes-tan.md b/.changeset/nice-eyes-tan.md new file mode 100644 index 00000000000..4b745e4614b --- /dev/null +++ b/.changeset/nice-eyes-tan.md @@ -0,0 +1,5 @@ +--- +"@firebase/firestore": minor +--- + +Add support for reading and writing Firestore vectors. From 022a646697cf8af21f34221bfe264430b1c2adc8 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:41:42 -0600 Subject: [PATCH 14/15] Update nice-eyes-tan.md --- .changeset/nice-eyes-tan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/nice-eyes-tan.md b/.changeset/nice-eyes-tan.md index 4b745e4614b..e2a25bdbc63 100644 --- a/.changeset/nice-eyes-tan.md +++ b/.changeset/nice-eyes-tan.md @@ -1,5 +1,6 @@ --- "@firebase/firestore": minor +"firebase": minor --- Add support for reading and writing Firestore vectors. From 76f29727c27fbdfd59a9d13a648e449ee19d2df2 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:06:40 -0600 Subject: [PATCH 15/15] comment corrections --- docs-devsite/firestore_.md | 2 +- docs-devsite/firestore_lite.md | 2 +- packages/firestore/src/lite-api/vector_value.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs-devsite/firestore_.md b/docs-devsite/firestore_.md index 1f18e22733e..74e960c833b 100644 --- a/docs-devsite/firestore_.md +++ b/docs-devsite/firestore_.md @@ -157,7 +157,7 @@ https://github.com/firebase/firebase-js-sdk | [SnapshotMetadata](./firestore_.snapshotmetadata.md#snapshotmetadata_class) | Metadata about a snapshot, describing the state of the snapshot. | | [Timestamp](./firestore_.timestamp.md#timestamp_class) | A Timestamp 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). | | [Transaction](./firestore_.transaction.md#transaction_class) | A reference to a transaction.The Transaction object passed to a transaction's updateFunction provides the methods to read and write data within the transaction context. See [runTransaction()](./firestore_.md#runtransaction_6f03ec4). | -| [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | Represent a vector type in Firestore documents. Create an instance with . VectorValue | +| [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | Represents a vector type in Firestore documents. Create an instance with . VectorValue | | [WriteBatch](./firestore_.writebatch.md#writebatch_class) | A write batch, used to perform multiple writes as a single atomic unit.A WriteBatch 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. | ## Interfaces diff --git a/docs-devsite/firestore_lite.md b/docs-devsite/firestore_lite.md index fec41a15a70..da7d304e3d5 100644 --- a/docs-devsite/firestore_lite.md +++ b/docs-devsite/firestore_lite.md @@ -119,7 +119,7 @@ https://github.com/firebase/firebase-js-sdk | [QueryStartAtConstraint](./firestore_lite.querystartatconstraint.md#querystartatconstraint_class) | A QueryStartAtConstraint is used to exclude documents from the start of a result set returned by a Firestore query. QueryStartAtConstraints 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 QueryStartAtConstraint. | | [Timestamp](./firestore_lite.timestamp.md#timestamp_class) | A Timestamp 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). | | [Transaction](./firestore_lite.transaction.md#transaction_class) | A reference to a transaction.The Transaction object passed to a transaction's updateFunction provides the methods to read and write data within the transaction context. See [runTransaction()](./firestore_.md#runtransaction_6f03ec4). | -| [VectorValue](./firestore_lite.vectorvalue.md#vectorvalue_class) | Represent a vector type in Firestore documents. Create an instance with . VectorValue | +| [VectorValue](./firestore_lite.vectorvalue.md#vectorvalue_class) | Represents a vector type in Firestore documents. Create an instance with . VectorValue | | [WriteBatch](./firestore_lite.writebatch.md#writebatch_class) | A write batch, used to perform multiple writes as a single atomic unit.A WriteBatch 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. | ## Interfaces diff --git a/packages/firestore/src/lite-api/vector_value.ts b/packages/firestore/src/lite-api/vector_value.ts index 5f065d1ce53..a09f2799fb3 100644 --- a/packages/firestore/src/lite-api/vector_value.ts +++ b/packages/firestore/src/lite-api/vector_value.ts @@ -18,7 +18,7 @@ import { isPrimitiveArrayEqual } from '../util/array'; /** - * Represent a vector type in Firestore documents. + * Represents a vector type in Firestore documents. * Create an instance with {@link FieldValue.vector}. * * @class VectorValue