Skip to content

Commit 4bdc5fe

Browse files
committed
fix: index-spec matching for optional values and vectorConfig.
The few new optional values for indexes were added recently via #8939. When comparing the local index with the server index, the default value of optional index configurations should be taken into account depending on the database edition (standard or enterprise). I also found that the matching logic for vectorConfig was not working correctly, so I fixed that as well. Fixes #8859.
1 parent a10ea0f commit 4bdc5fe

File tree

3 files changed

+274
-133
lines changed

3 files changed

+274
-133
lines changed

src/firestore/api.ts

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import * as clc from "colorette";
22

33
import { logger } from "../logger";
44
import * as utils from "../utils";
5+
import { deepEqual } from "../utils";
56
import * as validator from "./validator";
67

78
import * as types from "./api-types";
9+
import { DatabaseEdition, Density } from "./api-types";
810
import * as Spec from "./api-spec";
911
import * as sort from "./api-sort";
1012
import * as util from "./util";
@@ -74,8 +76,10 @@ export class FirestoreApi {
7476
databaseId,
7577
);
7678

79+
const database = await this.getDatabase(options.project, databaseId);
80+
const edition = database.databaseEdition ?? DatabaseEdition.STANDARD;
7781
const indexesToDelete = existingIndexes.filter((index) => {
78-
return !indexesToDeploy.some((spec) => this.indexMatchesSpec(index, spec));
82+
return !indexesToDeploy.some((spec) => this.indexMatchesSpec(index, spec, edition));
7983
});
8084

8185
// We only want to delete fields where there is nothing in the local file with the same
@@ -127,7 +131,7 @@ export class FirestoreApi {
127131
}
128132

129133
for (const index of indexesToDeploy) {
130-
const exists = existingIndexes.some((x) => this.indexMatchesSpec(x, index));
134+
const exists = existingIndexes.some((x) => this.indexMatchesSpec(x, index, edition));
131135
if (exists) {
132136
logger.debug(`Skipping existing index: ${JSON.stringify(index)}`);
133137
} else {
@@ -325,8 +329,11 @@ export class FirestoreApi {
325329
validator.assertType("multikey", index.multikey, "boolean");
326330
}
327331

328-
if (index.unique) {
332+
if (index.unique !== undefined) {
329333
validator.assertType("unique", index.unique, "boolean");
334+
// TODO(b/439901837): Remove this check and update indexMatchesSpec once
335+
// unique index configuration is supported.
336+
throw new FirebaseError("The `unique` index configuration is not supported yet.");
330337
}
331338

332339
validator.assertHas(index, "fields");
@@ -479,10 +486,62 @@ export class FirestoreApi {
479486
return this.apiClient.delete(`/${url}`);
480487
}
481488

489+
/**
490+
* Returns true if the given values match. If either one is undefined, the default value is used for comparison.
491+
* @param lhs the first value.
492+
* @param rhs the second value.
493+
*/
494+
optionalValueMatches<T>(lhs: T | undefined, rhs: T | undefined, defaultValue: T): boolean {
495+
lhs = lhs === undefined ? defaultValue : lhs;
496+
rhs = rhs === undefined ? defaultValue : rhs;
497+
return lhs === rhs;
498+
}
499+
500+
/**
501+
* Returns true if the given ApiScope values match.
502+
* If either one is undefined, the default value is used for comparison.
503+
* @param lhs the first ApiScope value.
504+
* @param rhs the second ApiScope value.
505+
*/
506+
optionalApiScopeMatches(
507+
lhs: types.ApiScope | undefined,
508+
rhs: types.ApiScope | undefined,
509+
): boolean {
510+
return this.optionalValueMatches<types.ApiScope>(lhs, rhs, types.ApiScope.ANY_API);
511+
}
512+
513+
/**
514+
* Returns true if the given Density values match.
515+
* If either one is undefined, the default value is used for comparison based on Database Edition.
516+
* @param lhs the first ApiScope value.
517+
* @param rhs the second ApiScope value.
518+
* @param edition the database edition used to determine the default value.
519+
*/
520+
optionalDensityMatches(
521+
lhs: Density | undefined,
522+
rhs: Density | undefined,
523+
edition: types.DatabaseEdition,
524+
) {
525+
const defaultValue =
526+
edition === DatabaseEdition.STANDARD ? types.Density.SPARSE_ALL : types.Density.DENSE;
527+
return this.optionalValueMatches<types.Density>(lhs, rhs, defaultValue);
528+
}
529+
530+
/**
531+
* Returns true if the given Multikey values match.
532+
* If either one is undefined, the default value is used for comparison.
533+
* @param lhs the first ApiScope value.
534+
* @param rhs the second ApiScope value.
535+
*/
536+
optionalMultikeyMatches(lhs: boolean | undefined, rhs: boolean | undefined) {
537+
const defaultValue = false;
538+
return this.optionalValueMatches<boolean>(lhs, rhs, defaultValue);
539+
}
540+
482541
/**
483542
* Determine if an API Index and a Spec Index are functionally equivalent.
484543
*/
485-
indexMatchesSpec(index: types.Index, spec: Spec.Index): boolean {
544+
indexMatchesSpec(index: types.Index, spec: Spec.Index, edition: types.DatabaseEdition): boolean {
486545
const collection = util.parseIndexName(index.name).collectionGroupId;
487546
if (collection !== spec.collectionGroup) {
488547
return false;
@@ -492,21 +551,24 @@ export class FirestoreApi {
492551
return false;
493552
}
494553

495-
if (index.apiScope !== spec.apiScope) {
554+
// apiScope is an optional value and may be missing in firestore.indexes.json,
555+
// and may also be missing from the server value (when default is picked).
556+
if (!this.optionalApiScopeMatches(index.apiScope, spec.apiScope)) {
496557
return false;
497558
}
498559

499-
if (index.density !== spec.density) {
560+
// density is an optional value and may be missing in firestore.indexes.json,
561+
// and may also be missing from the server value (when default is picked).
562+
if (!this.optionalDensityMatches(index.density, spec.density, edition)) {
500563
return false;
501564
}
502-
503-
if (index.multikey !== spec.multikey) {
565+
// multikey is an optional value and may be missing in firestore.indexes.json,
566+
// and may also be missing from the server value (when default is picked).
567+
if (!this.optionalMultikeyMatches(index.multikey, spec.multikey)) {
504568
return false;
505569
}
506570

507-
if (index.unique !== spec.unique) {
508-
return false;
509-
}
571+
// TODO(b/439901837): Compare `unique` index configuration when it's supported.
510572

511573
if (index.fields.length !== spec.fields.length) {
512574
return false;
@@ -529,7 +591,8 @@ export class FirestoreApi {
529591
return false;
530592
}
531593

532-
if (iField.vectorConfig !== sField.vectorConfig) {
594+
// Note: vectorConfig is an object, and using '!==' should not be used.
595+
if (!deepEqual(iField.vectorConfig, sField.vectorConfig)) {
533596
return false;
534597
}
535598

0 commit comments

Comments
 (0)