Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Vector Search #2006

Merged
merged 70 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
36a50cb
Implement vector field type
wu-hui Sep 15, 2023
415c94e
Merge branch 'main' into wuandy/VectorType
wu-hui Sep 15, 2023
3551acb
test
wu-hui Sep 19, 2023
248af30
Merge branch 'main' into wuandy/VectorType
wu-hui Nov 27, 2023
6042c4d
Added extra logs and vector type test
wu-hui Nov 29, 2023
764800b
Add vector watch test
wu-hui Dec 5, 2023
93ae1d7
Add vectorvalue to dts.
wu-hui Jan 17, 2024
a3d57e3
get d.ts right.
wu-hui Jan 10, 2024
68cafe1
VectorQuery and QueryUtil setup
wu-hui Jan 10, 2024
f8f1ecb
Stage proto
wu-hui Jan 11, 2024
9f973a6
Actual serialization
wu-hui Jan 12, 2024
57a3f25
Update proto
wu-hui Jan 15, 2024
e15721d
More integration tests
wu-hui Jan 15, 2024
3898181
fix some tests. Also what to do with mismatch dimentions
wu-hui Jan 15, 2024
069a3e4
Add unit tests and minor fixes
wu-hui Jan 16, 2024
4b8b624
Add comments and delete stream()
wu-hui Jan 17, 2024
cff8b5b
Fix rebase.
wu-hui Jan 17, 2024
2f1b964
Address Mark's comments.
wu-hui Jan 23, 2024
9ebd42a
Merge branch 'main' into wuandy/VectorType
wu-hui Jan 23, 2024
1132ff3
get d.ts right.
wu-hui Jan 10, 2024
ddec250
VectorQuery and QueryUtil setup
wu-hui Jan 10, 2024
86ce16e
Stage proto
wu-hui Jan 11, 2024
df0b01a
Actual serialization
wu-hui Jan 12, 2024
1755a4e
Update proto
wu-hui Jan 15, 2024
8da86bc
More integration tests
wu-hui Jan 15, 2024
b8a043c
fix some tests. Also what to do with mismatch dimentions
wu-hui Jan 15, 2024
629ed38
Add unit tests and minor fixes
wu-hui Jan 16, 2024
5ca985f
Add comments and delete stream()
wu-hui Jan 17, 2024
66cab1f
Fix rebase.
wu-hui Jan 17, 2024
30f9977
Address comments
wu-hui Jan 23, 2024
2ae6cfb
Merge remote-tracking branch 'origin/wuandy/FindNearestImpl' into wua…
wu-hui Jan 23, 2024
a6af2da
Address comments
wu-hui Jan 23, 2024
1943803
No retry with cursor for vector for now.
wu-hui Jan 29, 2024
e535fd1
Fix error message test
wu-hui Jan 31, 2024
0af118f
Fix broken array equality
wu-hui Jan 31, 2024
41ab580
Implementing IndexTestHelper and updating findNearest tests to use th…
MarkDuckworth Feb 29, 2024
bcf7f45
Add dot product support.
MarkDuckworth Mar 4, 2024
625e8d7
Merge branch 'main' of github.com:googleapis/nodejs-firestore into ma…
MarkDuckworth Mar 4, 2024
b512348
Adding missing file
MarkDuckworth Mar 4, 2024
0a9ab60
Lint
MarkDuckworth Mar 4, 2024
6b52042
Updating API reference docs.
MarkDuckworth Mar 5, 2024
afc4f99
Fixing API reference docs for TSDoc output on CGC.
MarkDuckworth Mar 5, 2024
3d0267f
Fix API reference docs.
MarkDuckworth Mar 6, 2024
c3a337e
Supporting Vector field order onSnapshot.
MarkDuckworth Mar 11, 2024
f0c6228
Add missing map-type file.
MarkDuckworth Mar 11, 2024
8436c1e
Cleanup and linting.
MarkDuckworth Mar 11, 2024
2fcf70a
Supporting Proto3 JSON with vector.
MarkDuckworth Mar 12, 2024
adb01c1
Additional test for vector in a map.
MarkDuckworth Mar 13, 2024
d916197
Merge branch 'main' of github.com:googleapis/nodejs-firestore into ma…
MarkDuckworth Mar 14, 2024
d3254cd
Update deps.
MarkDuckworth Mar 14, 2024
f0ed9bc
Add DOT_PRODUCT to d.ts
MarkDuckworth Mar 14, 2024
4791b1b
Merge branch 'main' of github.com:googleapis/nodejs-firestore into ma…
MarkDuckworth Mar 15, 2024
f2986f8
Merge branch 'main' of github.com:googleapis/nodejs-firestore into ma…
MarkDuckworth Mar 15, 2024
3a9e2ab
Merge branch 'main' of github.com:googleapis/nodejs-firestore into ma…
MarkDuckworth Mar 15, 2024
0d1b967
Staging protos
MarkDuckworth Mar 15, 2024
9d598a1
Types update for admin protos.
MarkDuckworth Mar 15, 2024
4513c24
License header fixes.
MarkDuckworth Mar 19, 2024
35303af
Fix docs CI for jsdoc build.
MarkDuckworth Mar 19, 2024
a40366a
Updated api-report
MarkDuckworth Mar 21, 2024
6e3796d
More integration test cases.
MarkDuckworth Mar 21, 2024
e5a3d9c
Clarifying supported range on options.limit
MarkDuckworth Mar 21, 2024
3840563
Merge branch 'main' into markduckworth/findnearest
MarkDuckworth Mar 25, 2024
970a3f1
Revert changes from staging protos that were not applied in stable.
MarkDuckworth Mar 25, 2024
b98c3e8
Improving test comments/name for PR feedback.
MarkDuckworth Mar 25, 2024
563ef55
Merge Query Profile to Find Nearest
MarkDuckworth Mar 26, 2024
ee3f1d6
Merge branch 'main' of github.com:googleapis/nodejs-firestore into ma…
MarkDuckworth Mar 27, 2024
ccabafd
Revert rename of internal toProto method name. It is marked internal,…
MarkDuckworth Mar 27, 2024
43a5cd1
Update api-report.md
MarkDuckworth Mar 27, 2024
a0f103e
Re-adding two internal methods to the Query class to minimize the ris…
MarkDuckworth Mar 27, 2024
bb56524
Merge branch 'main' of github.com:googleapis/nodejs-firestore into ma…
MarkDuckworth Mar 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 123 additions & 44 deletions api-report/firestore.api.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dev/src/aggregate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*!
/**
* Copyright 2023 Google LLC. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
2 changes: 1 addition & 1 deletion dev/src/collection-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class CollectionGroup<
// Partition queries require explicit ordering by __name__.
const queryWithDefaultOrder = this.orderBy(FieldPath.documentId());
const request: api.IPartitionQueryRequest =
queryWithDefaultOrder.toProto();
queryWithDefaultOrder._toProto();

// Since we are always returning an extra partition (with an empty endBefore
// cursor), we reduce the desired partition count by one.
Expand Down
103 changes: 62 additions & 41 deletions dev/src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {ApiMapValue, ProtobufJsValue} from './types';
import {validateObject} from './validate';

import api = google.firestore.v1;
import {RESERVED_MAP_KEY, RESERVED_MAP_KEY_VECTOR_VALUE} from './map-type';

/*!
* @module firestore/convert
Expand Down Expand Up @@ -112,53 +113,72 @@ function bytesFromJson(bytesValue: string | Uint8Array): Uint8Array {
* @return The string value for 'valueType'.
*/
export function detectValueType(proto: ProtobufJsValue): string {
let valueType: string | undefined;

if (proto.valueType) {
return proto.valueType;
}
valueType = proto.valueType;
} else {
const detectedValues: string[] = [];

const detectedValues: string[] = [];
if (proto.stringValue !== undefined) {
detectedValues.push('stringValue');
}
if (proto.booleanValue !== undefined) {
detectedValues.push('booleanValue');
}
if (proto.integerValue !== undefined) {
detectedValues.push('integerValue');
}
if (proto.doubleValue !== undefined) {
detectedValues.push('doubleValue');
}
if (proto.timestampValue !== undefined) {
detectedValues.push('timestampValue');
}
if (proto.referenceValue !== undefined) {
detectedValues.push('referenceValue');
}
if (proto.arrayValue !== undefined) {
detectedValues.push('arrayValue');
}
if (proto.nullValue !== undefined) {
detectedValues.push('nullValue');
}
if (proto.mapValue !== undefined) {
detectedValues.push('mapValue');
}
if (proto.geoPointValue !== undefined) {
detectedValues.push('geoPointValue');
}
if (proto.bytesValue !== undefined) {
detectedValues.push('bytesValue');
}

if (proto.stringValue !== undefined) {
detectedValues.push('stringValue');
}
if (proto.booleanValue !== undefined) {
detectedValues.push('booleanValue');
}
if (proto.integerValue !== undefined) {
detectedValues.push('integerValue');
}
if (proto.doubleValue !== undefined) {
detectedValues.push('doubleValue');
}
if (proto.timestampValue !== undefined) {
detectedValues.push('timestampValue');
}
if (proto.referenceValue !== undefined) {
detectedValues.push('referenceValue');
}
if (proto.arrayValue !== undefined) {
detectedValues.push('arrayValue');
}
if (proto.nullValue !== undefined) {
detectedValues.push('nullValue');
}
if (proto.mapValue !== undefined) {
detectedValues.push('mapValue');
}
if (proto.geoPointValue !== undefined) {
detectedValues.push('geoPointValue');
}
if (proto.bytesValue !== undefined) {
detectedValues.push('bytesValue');
if (detectedValues.length !== 1) {
throw new Error(
`Unable to infer type value from '${JSON.stringify(proto)}'.`
);
}

valueType = detectedValues[0];
}

if (detectedValues.length !== 1) {
throw new Error(
`Unable to infer type value from '${JSON.stringify(proto)}'.`
);
// Special handling of mapValues used to represent other data types
if (valueType === 'mapValue') {
const fields = proto.mapValue?.fields;
if (fields) {
const props = Object.keys(fields);
if (
props.indexOf(RESERVED_MAP_KEY) !== -1 &&
detectValueType(fields[RESERVED_MAP_KEY]) === 'stringValue' &&
fields[RESERVED_MAP_KEY].stringValue === RESERVED_MAP_KEY_VECTOR_VALUE
) {
valueType = 'vectorValue';
}
}
}

return detectedValues[0];
return valueType;
}

/**
Expand Down Expand Up @@ -240,7 +260,8 @@ export function valueFromJson(fieldValue: api.IValue): api.IValue {
},
};
}
case 'mapValue': {
case 'mapValue':
case 'vectorValue': {
const mapValue: ApiMapValue = {};
const fields = fieldValue.mapValue!.fields;
if (fields) {
Expand Down
66 changes: 65 additions & 1 deletion dev/src/field-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import * as proto from '../protos/firestore_v1_proto_api';

import {FieldPath} from './path';
import {Serializer, validateUserInput} from './serializer';
import {isPrimitiveArrayEqual} from './util';
import {
invalidArgumentMessage,
validateMinNumberOfArguments,
Expand All @@ -30,6 +31,58 @@ import {

import api = proto.google.firestore.v1;

/**
* Represent a vector type in Firestore documents.
* Create an instance with {@link FieldValue.vector}.
*
* @class VectorValue
*/
export class VectorValue implements firestore.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.
*/
public toArray(): number[] {
return this._values.map(n => n);
}

/**
* @private
* @internal
*/
_toProto(serializer: Serializer): api.IValue {
return serializer.encodeVector(this._values);
}

/**
* @private
* @internal
*/
static _fromProto(valueArray: api.IValue): VectorValue {
const values = valueArray.arrayValue?.values?.map(v => {
return v.doubleValue!;
});
return new VectorValue(values);
}

/**
* 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);
}
}

/**
* Sentinel values that can be used when writing documents with set(), create()
* or update().
Expand All @@ -40,6 +93,17 @@ export class FieldValue implements firestore.FieldValue {
/** @private */
constructor() {}

/**
* 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.
*/
static vector(values?: number[]): VectorValue {
return new VectorValue(values);
}

/**
* Returns a sentinel for use with update() or set() with {merge:true} to mark
* a field for deletion.
Expand Down Expand Up @@ -220,7 +284,7 @@ export class FieldValue implements firestore.FieldValue {
* An internal interface shared by all field transforms.
*
* A 'FieldTransform` subclass should implement '.includeInDocumentMask',
* '.includeInDocumentTransform' and 'toProto' (if '.includeInDocumentTransform'
* '.includeInDocumentTransform' and '_toProto' (if '.includeInDocumentTransform'
* is 'true').
*
* @private
Expand Down
9 changes: 7 additions & 2 deletions dev/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,17 @@ export {
QuerySnapshot,
Query,
} from './reference';
export type {AggregateQuery, AggregateQuerySnapshot} from './reference';
export type {
AggregateQuery,
AggregateQuerySnapshot,
VectorQuery,
VectorQuerySnapshot,
} from './reference';
export {BulkWriter} from './bulk-writer';
export type {BulkWriterError} from './bulk-writer';
export type {BundleBuilder} from './bundle';
export {DocumentSnapshot, QueryDocumentSnapshot} from './document';
export {FieldValue} from './field-value';
export {FieldValue, VectorValue} from './field-value';
export {Filter} from './filter';
export {WriteBatch, WriteResult} from './write-batch';
export {Transaction} from './transaction';
Expand Down
19 changes: 19 additions & 0 deletions dev/src/map-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*!
* 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.
*/

export const RESERVED_MAP_KEY = '__type__';
export const RESERVED_MAP_KEY_VECTOR_VALUE = '__vector__';
export const VECTOR_MAP_VECTORS_KEY = 'value';
30 changes: 29 additions & 1 deletion dev/src/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ enum TypeOrder {
REF = 6,
GEO_POINT = 7,
ARRAY = 8,
OBJECT = 9,
VECTOR = 9,
OBJECT = 10,
}

/*!
Expand Down Expand Up @@ -67,6 +68,8 @@ function typeOrder(val: api.IValue): TypeOrder {
return TypeOrder.REF;
case 'mapValue':
return TypeOrder.OBJECT;
case 'vectorValue':
return TypeOrder.VECTOR;
default:
throw new Error('Unexpected value type: ' + valueType);
}
Expand Down Expand Up @@ -225,6 +228,26 @@ function compareObjects(left: ApiMapValue, right: ApiMapValue): number {
return primitiveComparator(leftKeys.length, rightKeys.length);
}

/*!
* @private
* @internal
*/
function compareVectors(left: ApiMapValue, right: ApiMapValue): number {
// The vector is a map, but only vector value is compared.
const leftArray = left?.['value']?.arrayValue?.values ?? [];
const rightArray = right?.['value']?.arrayValue?.values ?? [];

const lengthCompare = primitiveComparator(
leftArray.length,
rightArray.length
);
if (lengthCompare !== 0) {
return lengthCompare;
}

return compareArrays(leftArray, rightArray);
}

/*!
* @private
* @internal
Expand Down Expand Up @@ -267,6 +290,11 @@ export function compare(left: api.IValue, right: api.IValue): number {
left.mapValue!.fields || {},
right.mapValue!.fields || {}
);
case TypeOrder.VECTOR:
return compareVectors(
left.mapValue!.fields || {},
right.mapValue!.fields || {}
);
default:
throw new Error(`Encountered unknown type order: ${leftType}`);
}
Expand Down
Loading
Loading