Skip to content

Commit d798016

Browse files
committed
sort document keys by numeric ids
1 parent b372ee7 commit d798016

File tree

2 files changed

+125
-1
lines changed

2 files changed

+125
-1
lines changed

Firestore/Example/Tests/Integration/API/FIRQueryTests.mm

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,95 @@ - (void)testCollectionGroupQueriesWithStartAtEndAtWithArbitraryDocumentIDs {
804804
XCTAssertEqualObjects(ids, (@[ @"cg-doc2" ]));
805805
}
806806

807+
- (void)testSnapshotListenerSortsQueryByDocumentIdInTheSameOrderAsServer {
808+
FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
809+
@"A" : @{@"a" : @1},
810+
@"a" : @{@"a" : @1},
811+
@"Aa" : @{@"a" : @1},
812+
@"7" : @{@"a" : @1},
813+
@"12" : @{@"a" : @1},
814+
@"__id7__" : @{@"a" : @1},
815+
@"__id12__" : @{@"a" : @1},
816+
@"__id-2__" : @{@"a" : @1},
817+
@"__id1_" : @{@"a" : @1},
818+
@"_id1__" : @{@"a" : @1},
819+
@"__id9223372036854775807__" : @{@"a" : @1},
820+
@"__id-9223372036854775808__" : @{@"a" : @1},
821+
}];
822+
823+
FIRQuery *query = [collRef queryOrderedByFieldPath:[FIRFieldPath documentID]];
824+
NSArray<NSString *> *expectedDocs = @[
825+
@"__id-9223372036854775808__", @"__id-2__", @"__id7__", @"__id12__",
826+
@"__id9223372036854775807__", @"12", @"7", @"A", @"Aa", @"__id1_", @"_id1__", @"a"
827+
];
828+
FIRQuerySnapshot *getSnapshot = [self readDocumentSetForRef:query];
829+
XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(getSnapshot), expectedDocs);
830+
831+
id<FIRListenerRegistration> registration =
832+
[query addSnapshotListener:self.eventAccumulator.valueEventHandler];
833+
FIRQuerySnapshot *watchSnapshot = [self.eventAccumulator awaitEventWithName:@"Snapshot"];
834+
XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(watchSnapshot), expectedDocs);
835+
836+
[registration remove];
837+
}
838+
839+
- (void)testSnapshotListenerSortsFilteredQueryByDocumentIdInTheSameOrderAsServer {
840+
FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
841+
@"A" : @{@"a" : @1},
842+
@"a" : @{@"a" : @1},
843+
@"Aa" : @{@"a" : @1},
844+
@"7" : @{@"a" : @1},
845+
@"12" : @{@"a" : @1},
846+
@"__id7__" : @{@"a" : @1},
847+
@"__id12__" : @{@"a" : @1},
848+
@"__id-2__" : @{@"a" : @1},
849+
@"__id1_" : @{@"a" : @1},
850+
@"_id1__" : @{@"a" : @1},
851+
@"__id9223372036854775807__" : @{@"a" : @1},
852+
@"__id-9223372036854775808__" : @{@"a" : @1},
853+
}];
854+
855+
FIRQuery *query = [[[collRef queryWhereFieldPath:[FIRFieldPath documentID]
856+
isGreaterThan:@"__id7__"]
857+
queryWhereFieldPath:[FIRFieldPath documentID]
858+
isLessThanOrEqualTo:@"A"] queryOrderedByFieldPath:[FIRFieldPath documentID]];
859+
NSArray<NSString *> *expectedDocs =
860+
@[ @"__id12__", @"__id9223372036854775807__", @"12", @"7", @"A" ];
861+
FIRQuerySnapshot *getSnapshot = [self readDocumentSetForRef:query];
862+
XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(getSnapshot), expectedDocs);
863+
864+
id<FIRListenerRegistration> registration =
865+
[query addSnapshotListener:self.eventAccumulator.valueEventHandler];
866+
FIRQuerySnapshot *watchSnapshot = [self.eventAccumulator awaitEventWithName:@"Snapshot"];
867+
XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(watchSnapshot), expectedDocs);
868+
869+
[registration remove];
870+
}
871+
872+
- (void)testSdkOrdersQueryByDocumentIdTheSameWayOnlineAndOffline {
873+
FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
874+
@"A" : @{@"a" : @1},
875+
@"a" : @{@"a" : @1},
876+
@"Aa" : @{@"a" : @1},
877+
@"7" : @{@"a" : @1},
878+
@"12" : @{@"a" : @1},
879+
@"__id7__" : @{@"a" : @1},
880+
@"__id12__" : @{@"a" : @1},
881+
@"__id-2__" : @{@"a" : @1},
882+
@"__id1_" : @{@"a" : @1},
883+
@"_id1__" : @{@"a" : @1},
884+
@"__id9223372036854775807__" : @{@"a" : @1},
885+
@"__id-9223372036854775808__" : @{@"a" : @1},
886+
}];
887+
888+
[self checkOnlineAndOfflineQuery:[collRef queryOrderedByFieldPath:[FIRFieldPath documentID]]
889+
matchesResult:@[
890+
@"__id-9223372036854775808__", @"__id-2__", @"__id7__", @"__id12__",
891+
@"__id9223372036854775807__", @"12", @"7", @"A", @"Aa", @"__id1_", @"_id1__",
892+
@"a"
893+
]];
894+
}
895+
807896
- (void)testCollectionGroupQueriesWithWhereFiltersOnArbitraryDocumentIDs {
808897
// Use .document() to get a random collection group name to use but ensure it starts with 'b'
809898
// for predictable ordering.

Firestore/core/src/model/base_path.h

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,18 @@ class BasePath {
150150
std::equal(begin(), end(), potential_child.begin());
151151
}
152152

153+
/**
154+
* Compares the current path against another Path object. Paths are compared
155+
* segment by segment, prioritizing numeric IDs (e.g., "__id123__") in numeric
156+
* ascending order, followed by string segments in lexicographical order.
157+
*/
153158
util::ComparisonResult CompareTo(const T& rhs) const {
154-
return util::CompareContainer(segments_, rhs.segments_);
159+
size_t min_size = std::min(size(), rhs.size());
160+
for (size_t i = 0; i < min_size; ++i) {
161+
auto cmp = compareSegments(segments_[i], rhs.segments_[i]);
162+
if (!util::Same(cmp)) return cmp;
163+
}
164+
return util::Compare(size(), rhs.size());
155165
}
156166

157167
friend bool operator==(const BasePath& lhs, const BasePath& rhs) {
@@ -174,6 +184,31 @@ class BasePath {
174184

175185
private:
176186
SegmentsT segments_;
187+
188+
static util::ComparisonResult compareSegments(const std::string& lhs,
189+
const std::string& rhs) {
190+
bool isLhsNumeric = isNumericId(lhs);
191+
bool isRhsNumeric = isNumericId(rhs);
192+
193+
if (isLhsNumeric && !isRhsNumeric) {
194+
return util::ComparisonResult::Ascending;
195+
} else if (!isLhsNumeric && isRhsNumeric) {
196+
return util::ComparisonResult::Descending;
197+
} else if (isLhsNumeric && isRhsNumeric) {
198+
return util::Compare(extractNumericId(lhs), extractNumericId(rhs));
199+
} else {
200+
return util::Compare(lhs, rhs);
201+
}
202+
}
203+
204+
static bool isNumericId(const std::string& segment) {
205+
return segment.substr(0, 4) == "__id" &&
206+
segment.substr(segment.size() - 2) == "__";
207+
}
208+
209+
static long extractNumericId(const std::string& segment) {
210+
return std::stol(segment.substr(4, segment.size() - 2));
211+
}
177212
};
178213

179214
} // namespace impl

0 commit comments

Comments
 (0)