Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **firestore**: Fixed issue where `__name__` fields with DESCENDING order were incorrectly filtered from index listings, causing duplicate index issues (#7629) and deployment conflicts (#8859). The fix now preserves `__name__` fields with explicit DESCENDING order while filtering out implicit ASCENDING `__name__` fields.
21 changes: 20 additions & 1 deletion src/firestore/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ export class FirestoreApi {
apiClient = new Client({ urlPrefix: firestoreOrigin(), apiVersion: "v1" });
printer = new PrettyPrint();

/**
* Process indexes by filtering out implicit __name__ fields with ASCENDING order.
* Keeps explicit __name__ fields with DESCENDING order.
* @param indexes Array of indexes to process
* @returns Processed array of indexes with filtered fields
*/
public static processIndexes(indexes: types.Index[]): types.Index[] {
return indexes.map((index: types.Index): types.Index => {
const fields = index.fields.filter(
(field) => field.fieldPath !== "__name__" || field.order === types.Order.DESCENDING,
);

return {
...index,
fields,
};
});
}

/**
* Deploy an index specification to the specified project.
* @param options the CLI options.
Expand Down Expand Up @@ -183,7 +202,7 @@ export class FirestoreApi {
return [];
}

return indexes;
return FirestoreApi.processIndexes(indexes);
}

/**
Expand Down
123 changes: 123 additions & 0 deletions src/firestore/indexes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,129 @@ describe("IndexSpecMatching", () => {
});
});

describe("IndexListingWithNameFields", () => {
it("should filter out __name__ fields with ASCENDING order", () => {
const mockIndexes: API.Index[] = [
{
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123",
queryScope: API.QueryScope.COLLECTION,
fields: [
{ fieldPath: "foo", order: API.Order.ASCENDING },
{ fieldPath: "__name__", order: API.Order.ASCENDING },
],
state: API.State.READY,
},
];

const result = FirestoreApi.processIndexes(mockIndexes);

expect(result[0].fields).to.have.length(1);
expect(result[0].fields[0].fieldPath).to.equal("foo");
});

it("should keep __name__ fields with DESCENDING order", () => {
const mockIndexes: API.Index[] = [
{
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123",
queryScope: API.QueryScope.COLLECTION,
fields: [
{ fieldPath: "foo", order: API.Order.ASCENDING },
{ fieldPath: "__name__", order: API.Order.DESCENDING },
],
state: API.State.READY,
},
];

const result = FirestoreApi.processIndexes(mockIndexes);

expect(result[0].fields).to.have.length(2);
expect(result[0].fields[0].fieldPath).to.equal("foo");
expect(result[0].fields[1].fieldPath).to.equal("__name__");
expect(result[0].fields[1].order).to.equal(API.Order.DESCENDING);
});

it("should distinguish between indexes that differ only by __name__ order", () => {
const mockIndexes: API.Index[] = [
{
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123",
queryScope: API.QueryScope.COLLECTION,
fields: [
{ fieldPath: "foo", order: API.Order.ASCENDING },
{ fieldPath: "__name__", order: API.Order.ASCENDING },
],
state: API.State.READY,
},
{
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/def456",
queryScope: API.QueryScope.COLLECTION,
fields: [
{ fieldPath: "foo", order: API.Order.ASCENDING },
{ fieldPath: "__name__", order: API.Order.DESCENDING },
],
state: API.State.READY,
},
];

const result = FirestoreApi.processIndexes(mockIndexes);

// First index should have __name__ field filtered out
expect(result[0].fields).to.have.length(1);
expect(result[0].fields[0].fieldPath).to.equal("foo");

// Second index should keep __name__ field with DESCENDING order
expect(result[1].fields).to.have.length(2);
expect(result[1].fields[0].fieldPath).to.equal("foo");
expect(result[1].fields[1].fieldPath).to.equal("__name__");
expect(result[1].fields[1].order).to.equal(API.Order.DESCENDING);

// The two processed indexes should be different (fixing the duplicate issue)
expect(JSON.stringify(result[0].fields)).to.not.equal(JSON.stringify(result[1].fields));
});

it("should keep all non-__name__ fields regardless of order", () => {
const mockIndexes: API.Index[] = [
{
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123",
queryScope: API.QueryScope.COLLECTION,
fields: [
{ fieldPath: "foo", order: API.Order.ASCENDING },
{ fieldPath: "bar", order: API.Order.DESCENDING },
{ fieldPath: "__name__", order: API.Order.ASCENDING },
],
state: API.State.READY,
},
];

const result = FirestoreApi.processIndexes(mockIndexes);

expect(result[0].fields).to.have.length(2);
expect(result[0].fields[0].fieldPath).to.equal("foo");
expect(result[0].fields[0].order).to.equal(API.Order.ASCENDING);
expect(result[0].fields[1].fieldPath).to.equal("bar");
expect(result[0].fields[1].order).to.equal(API.Order.DESCENDING);
});

it("should handle indexes with no __name__ fields", () => {
const mockIndexes: API.Index[] = [
{
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123",
queryScope: API.QueryScope.COLLECTION,
fields: [
{ fieldPath: "foo", order: API.Order.ASCENDING },
{ fieldPath: "bar", arrayConfig: API.ArrayConfig.CONTAINS },
],
state: API.State.READY,
},
];

const result = FirestoreApi.processIndexes(mockIndexes);

expect(result[0].fields).to.have.length(2);
expect(result[0].fields[0].fieldPath).to.equal("foo");
expect(result[0].fields[1].fieldPath).to.equal("bar");
});
});

describe("IndexSorting", () => {
it("should be able to handle empty arrays", () => {
expect(([] as Spec.Index[]).sort(sort.compareSpecIndex)).to.eql([]);
Expand Down