Skip to content

Commit f127fba

Browse files
committed
fix(firestore): smart filtering of __name__ fields in index listings
Preserve __name__ fields with DESCENDING order while filtering out implicit ASCENDING ones to fix deployment conflicts. - Fixes duplicate index issues (#7629) - Fixes deployment conflicts (#8859) - Add comprehensive test coverage Fixes #7629, #8859
1 parent fe8ae92 commit f127fba

File tree

3 files changed

+210
-1
lines changed

3 files changed

+210
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

src/firestore/api.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,22 @@ export class FirestoreApi {
183183
return [];
184184
}
185185

186-
return indexes;
186+
return indexes.map((index: any): types.Index => {
187+
// Filter out implicit __name__ fields with ASCENDING order, keep explicit DESCENDING ones.
188+
const fields = index.fields.filter((field: types.IndexField) => {
189+
if (field.fieldPath !== "__name__") {
190+
return true;
191+
}
192+
return field.order === types.Order.DESCENDING;
193+
});
194+
195+
return {
196+
name: index.name,
197+
state: index.state,
198+
queryScope: index.queryScope,
199+
fields,
200+
};
201+
});
187202
}
188203

189204
/**

src/firestore/indexes.spec.ts

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,199 @@ describe("IndexSpecMatching", () => {
447447
});
448448
});
449449

450+
describe("IndexListingWithNameFields", () => {
451+
it("should filter out __name__ fields with ASCENDING order", () => {
452+
const mockIndexes = [
453+
{
454+
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123",
455+
queryScope: "COLLECTION",
456+
fields: [
457+
{ fieldPath: "foo", order: "ASCENDING" },
458+
{ fieldPath: "__name__", order: "ASCENDING" },
459+
],
460+
state: "READY",
461+
},
462+
];
463+
464+
const result = mockIndexes.map((index: any): API.Index => {
465+
const fields = index.fields.filter((field: API.IndexField) => {
466+
if (field.fieldPath !== "__name__") {
467+
return true;
468+
}
469+
return field.order === API.Order.DESCENDING;
470+
});
471+
472+
return {
473+
name: index.name,
474+
state: index.state,
475+
queryScope: index.queryScope,
476+
fields,
477+
};
478+
});
479+
480+
expect(result[0].fields).to.have.length(1);
481+
expect(result[0].fields[0].fieldPath).to.equal("foo");
482+
});
483+
484+
it("should keep __name__ fields with DESCENDING order", () => {
485+
const mockIndexes = [
486+
{
487+
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123",
488+
queryScope: "COLLECTION",
489+
fields: [
490+
{ fieldPath: "foo", order: "ASCENDING" },
491+
{ fieldPath: "__name__", order: "DESCENDING" },
492+
],
493+
state: "READY",
494+
},
495+
];
496+
497+
const result = mockIndexes.map((index: any): API.Index => {
498+
const fields = index.fields.filter((field: API.IndexField) => {
499+
if (field.fieldPath !== "__name__") {
500+
return true;
501+
}
502+
return field.order === API.Order.DESCENDING;
503+
});
504+
505+
return {
506+
name: index.name,
507+
state: index.state,
508+
queryScope: index.queryScope,
509+
fields,
510+
};
511+
});
512+
513+
expect(result[0].fields).to.have.length(2);
514+
expect(result[0].fields[0].fieldPath).to.equal("foo");
515+
expect(result[0].fields[1].fieldPath).to.equal("__name__");
516+
expect(result[0].fields[1].order).to.equal("DESCENDING");
517+
});
518+
519+
it("should distinguish between indexes that differ only by __name__ order", () => {
520+
const mockIndexes = [
521+
{
522+
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123",
523+
queryScope: "COLLECTION",
524+
fields: [
525+
{ fieldPath: "foo", order: "ASCENDING" },
526+
{ fieldPath: "__name__", order: "ASCENDING" },
527+
],
528+
state: "READY",
529+
},
530+
{
531+
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/def456",
532+
queryScope: "COLLECTION",
533+
fields: [
534+
{ fieldPath: "foo", order: "ASCENDING" },
535+
{ fieldPath: "__name__", order: "DESCENDING" },
536+
],
537+
state: "READY",
538+
},
539+
];
540+
541+
const result = mockIndexes.map((index: any): API.Index => {
542+
const fields = index.fields.filter((field: API.IndexField) => {
543+
if (field.fieldPath !== "__name__") {
544+
return true;
545+
}
546+
return field.order === API.Order.DESCENDING;
547+
});
548+
549+
return {
550+
name: index.name,
551+
state: index.state,
552+
queryScope: index.queryScope,
553+
fields,
554+
};
555+
});
556+
557+
// First index should have __name__ field filtered out
558+
expect(result[0].fields).to.have.length(1);
559+
expect(result[0].fields[0].fieldPath).to.equal("foo");
560+
561+
// Second index should keep __name__ field with DESCENDING order
562+
expect(result[1].fields).to.have.length(2);
563+
expect(result[1].fields[0].fieldPath).to.equal("foo");
564+
expect(result[1].fields[1].fieldPath).to.equal("__name__");
565+
expect(result[1].fields[1].order).to.equal("DESCENDING");
566+
567+
// The two processed indexes should be different (fixing the duplicate issue)
568+
expect(JSON.stringify(result[0].fields)).to.not.equal(JSON.stringify(result[1].fields));
569+
});
570+
571+
it("should keep all non-__name__ fields regardless of order", () => {
572+
const mockIndexes = [
573+
{
574+
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123",
575+
queryScope: "COLLECTION",
576+
fields: [
577+
{ fieldPath: "foo", order: "ASCENDING" },
578+
{ fieldPath: "bar", order: "DESCENDING" },
579+
{ fieldPath: "__name__", order: "ASCENDING" },
580+
],
581+
state: "READY",
582+
},
583+
];
584+
585+
const result = mockIndexes.map((index: any): API.Index => {
586+
const fields = index.fields.filter((field: API.IndexField) => {
587+
if (field.fieldPath !== "__name__") {
588+
return true;
589+
}
590+
return field.order === API.Order.DESCENDING;
591+
});
592+
593+
return {
594+
name: index.name,
595+
state: index.state,
596+
queryScope: index.queryScope,
597+
fields,
598+
};
599+
});
600+
601+
expect(result[0].fields).to.have.length(2);
602+
expect(result[0].fields[0].fieldPath).to.equal("foo");
603+
expect(result[0].fields[0].order).to.equal("ASCENDING");
604+
expect(result[0].fields[1].fieldPath).to.equal("bar");
605+
expect(result[0].fields[1].order).to.equal("DESCENDING");
606+
});
607+
608+
it("should handle indexes with no __name__ fields", () => {
609+
const mockIndexes = [
610+
{
611+
name: "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123",
612+
queryScope: "COLLECTION",
613+
fields: [
614+
{ fieldPath: "foo", order: "ASCENDING" },
615+
{ fieldPath: "bar", arrayConfig: "CONTAINS" },
616+
],
617+
state: "READY",
618+
},
619+
];
620+
621+
const result = mockIndexes.map((index: any): API.Index => {
622+
const fields = index.fields.filter((field: API.IndexField) => {
623+
if (field.fieldPath !== "__name__") {
624+
return true;
625+
}
626+
return field.order === API.Order.DESCENDING;
627+
});
628+
629+
return {
630+
name: index.name,
631+
state: index.state,
632+
queryScope: index.queryScope,
633+
fields,
634+
};
635+
});
636+
637+
expect(result[0].fields).to.have.length(2);
638+
expect(result[0].fields[0].fieldPath).to.equal("foo");
639+
expect(result[0].fields[1].fieldPath).to.equal("bar");
640+
});
641+
});
642+
450643
describe("IndexSorting", () => {
451644
it("should be able to handle empty arrays", () => {
452645
expect(([] as Spec.Index[]).sort(sort.compareSpecIndex)).to.eql([]);

0 commit comments

Comments
 (0)