Skip to content

Commit 52afbf0

Browse files
committed
include numeric values into documentid
1 parent 2b072d9 commit 52afbf0

File tree

2 files changed

+185
-1
lines changed

2 files changed

+185
-1
lines changed

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/FirestoreTest.java

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.firebase.firestore;
1616

1717
import static com.google.firebase.firestore.AccessHelper.getAsyncQueue;
18+
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.checkOnlineAndOfflineResultsMatch;
1819
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.isRunningAgainstEmulator;
1920
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.newTestSettings;
2021
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.provider;
@@ -63,6 +64,7 @@
6364
import java.util.Map;
6465
import java.util.concurrent.CountDownLatch;
6566
import java.util.concurrent.Semaphore;
67+
import java.util.stream.Collectors;
6668
import org.junit.After;
6769
import org.junit.Test;
6870
import org.junit.runner.RunWith;
@@ -1493,4 +1495,157 @@ public void testCanGetSameOrDifferentPersistentCacheIndexManager() {
14931495
assertNotSame(indexManager3, indexManager6);
14941496
assertNotSame(indexManager5, indexManager6);
14951497
}
1498+
1499+
@Test
1500+
public void snapshotListenerSortsQueryByDocumentIdsSameAsGetQuery() {
1501+
Map<String, Map<String, Object>> testDocs =
1502+
map(
1503+
"A", map("a", 1),
1504+
"a", map("a", 1),
1505+
"Aa", map("a", 1),
1506+
"7", map("a", 1),
1507+
"12", map("a", 1),
1508+
"__id7__", map("a", 1),
1509+
"__id12__", map("a", 1),
1510+
"__id-2__", map("a", 1),
1511+
"__id1_", map("a", 1),
1512+
"_id1__", map("a", 1),
1513+
"__id9223372036854775807__", map("a", 1),
1514+
"__id-9223372036854775808__", map("a", 1));
1515+
1516+
CollectionReference colRef = testCollectionWithDocs(testDocs);
1517+
1518+
// Run get query
1519+
Query orderedQuery = colRef.orderBy(FieldPath.documentId());
1520+
List<String> expectedDocIds =
1521+
Arrays.asList(
1522+
"__id-9223372036854775808__",
1523+
"__id-2__",
1524+
"__id7__",
1525+
"__id12__",
1526+
"__id9223372036854775807__",
1527+
"12",
1528+
"7",
1529+
"A",
1530+
"Aa",
1531+
"__id1_",
1532+
"_id1__",
1533+
"a");
1534+
1535+
QuerySnapshot getSnapshot = waitFor(orderedQuery.get());
1536+
List<String> getSnapshotDocIds =
1537+
getSnapshot.getDocuments().stream().map(ds -> ds.getId()).collect(Collectors.toList());
1538+
1539+
// Run query with snapshot listener
1540+
EventAccumulator<QuerySnapshot> eventAccumulator = new EventAccumulator<QuerySnapshot>();
1541+
ListenerRegistration registration =
1542+
orderedQuery.addSnapshotListener(eventAccumulator.listener());
1543+
1544+
List<String> watchSnapshotDocIds = new ArrayList<>();
1545+
try {
1546+
QuerySnapshot watchSnapshot = eventAccumulator.await();
1547+
watchSnapshotDocIds =
1548+
watchSnapshot.getDocuments().stream()
1549+
.map(documentSnapshot -> documentSnapshot.getId())
1550+
.collect(Collectors.toList());
1551+
} finally {
1552+
registration.remove();
1553+
}
1554+
1555+
// Assert that get and snapshot listener requests sort docs in the same, expected order
1556+
assertTrue(getSnapshotDocIds.equals(expectedDocIds));
1557+
assertTrue(watchSnapshotDocIds.equals(expectedDocIds));
1558+
}
1559+
1560+
@Test
1561+
public void snapshotListenerSortsFilteredQueryByDocumentIdsSameAsGetQuery() {
1562+
Map<String, Map<String, Object>> testDocs =
1563+
map(
1564+
"A", map("a", 1),
1565+
"a", map("a", 1),
1566+
"Aa", map("a", 1),
1567+
"7", map("a", 1),
1568+
"12", map("a", 1),
1569+
"__id7__", map("a", 1),
1570+
"__id12__", map("a", 1),
1571+
"__id-2__", map("a", 1),
1572+
"__id1_", map("a", 1),
1573+
"_id1__", map("a", 1),
1574+
"__id9223372036854775807__", map("a", 1),
1575+
"__id-9223372036854775808__", map("a", 1));
1576+
1577+
CollectionReference colRef = testCollectionWithDocs(testDocs);
1578+
1579+
// Run get query
1580+
Query filteredQuery =
1581+
colRef
1582+
.whereGreaterThan(FieldPath.documentId(), "__id7__")
1583+
.whereLessThanOrEqualTo(FieldPath.documentId(), "A")
1584+
.orderBy(FieldPath.documentId());
1585+
List<String> expectedDocIds =
1586+
Arrays.asList("__id12__", "__id9223372036854775807__", "12", "7", "A");
1587+
1588+
QuerySnapshot getSnapshot = waitFor(filteredQuery.get());
1589+
List<String> getSnapshotDocIds =
1590+
getSnapshot.getDocuments().stream().map(ds -> ds.getId()).collect(Collectors.toList());
1591+
1592+
// Run query with snapshot listener
1593+
EventAccumulator<QuerySnapshot> eventAccumulator = new EventAccumulator<QuerySnapshot>();
1594+
ListenerRegistration registration =
1595+
filteredQuery.addSnapshotListener(eventAccumulator.listener());
1596+
1597+
List<String> watchSnapshotDocIds = new ArrayList<>();
1598+
try {
1599+
QuerySnapshot watchSnapshot = eventAccumulator.await();
1600+
watchSnapshotDocIds =
1601+
watchSnapshot.getDocuments().stream()
1602+
.map(documentSnapshot -> documentSnapshot.getId())
1603+
.collect(Collectors.toList());
1604+
} finally {
1605+
registration.remove();
1606+
}
1607+
1608+
// Assert that get and snapshot listener requests sort docs in the same, expected order
1609+
assertTrue(getSnapshotDocIds.equals(expectedDocIds));
1610+
assertTrue(watchSnapshotDocIds.equals(expectedDocIds));
1611+
}
1612+
1613+
@Test
1614+
public void vectorFieldOrderOnlineAndOffline() throws Exception {
1615+
Map<String, Map<String, Object>> testDocs =
1616+
map(
1617+
"A", map("a", 1),
1618+
"a", map("a", 1),
1619+
"Aa", map("a", 1),
1620+
"7", map("a", 1),
1621+
"12", map("a", 1),
1622+
"__id7__", map("a", 1),
1623+
"__id12__", map("a", 1),
1624+
"__id-2__", map("a", 1),
1625+
"__id1_", map("a", 1),
1626+
"_id1__", map("a", 1),
1627+
"__id9223372036854775807__", map("a", 1),
1628+
"__id-9223372036854775808__", map("a", 1));
1629+
1630+
CollectionReference colRef = testCollectionWithDocs(testDocs);
1631+
// Test query
1632+
Query orderedQuery = colRef.orderBy(FieldPath.documentId());
1633+
List<String> expectedDocIds =
1634+
Arrays.asList(
1635+
"__id-9223372036854775808__",
1636+
"__id-2__",
1637+
"__id7__",
1638+
"__id12__",
1639+
"__id9223372036854775807__",
1640+
"12",
1641+
"7",
1642+
"A",
1643+
"Aa",
1644+
"__id1_",
1645+
"_id1__",
1646+
"a");
1647+
1648+
// Run query with snapshot listener
1649+
checkOnlineAndOfflineResultsMatch(orderedQuery, expectedDocIds.toArray(new String[0]));
1650+
}
14961651
}

firebase-firestore/src/main/java/com/google/firebase/firestore/model/BasePath.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,18 @@ public B keepFirst(int count) {
8383
return createPathWithSegments(segments.subList(0, count));
8484
}
8585

86+
/**
87+
* Compare the current path against another Path object. Paths are compared segment by segment,
88+
* prioritizing numeric IDs (e.g., "__id123__") in numeric ascending order, followed by string
89+
* segments in lexicographical order.
90+
*/
8691
@Override
8792
public int compareTo(@NonNull B o) {
8893
int i = 0;
8994
int myLength = length();
9095
int theirLength = o.length();
9196
while (i < myLength && i < theirLength) {
92-
int localCompare = getSegment(i).compareTo(o.getSegment(i));
97+
int localCompare = compareSegments(getSegment(i), o.getSegment(i));
9398
if (localCompare != 0) {
9499
return localCompare;
95100
}
@@ -98,6 +103,30 @@ public int compareTo(@NonNull B o) {
98103
return Util.compareIntegers(myLength, theirLength);
99104
}
100105

106+
private static int compareSegments(String lhs, String rhs) {
107+
boolean isLhsNumeric = isNumericId(lhs);
108+
boolean isRhsNumeric = isNumericId(rhs);
109+
110+
if (isLhsNumeric && !isRhsNumeric) { // Only lhs is numeric
111+
return -1;
112+
} else if (!isLhsNumeric && isRhsNumeric) { // Only rhs is numeric
113+
return 1;
114+
} else if (isLhsNumeric && isRhsNumeric) { // both numeric
115+
return Long.compare(extractNumericId(lhs), extractNumericId(rhs));
116+
} else { // both string
117+
return lhs.compareTo(rhs);
118+
}
119+
}
120+
121+
/** Checks if a segment is a numeric ID (starts with "__id" and ends with "__"). */
122+
private static boolean isNumericId(String segment) {
123+
return segment.startsWith("__id") && segment.endsWith("__");
124+
}
125+
126+
private static long extractNumericId(String segment) {
127+
return Long.parseLong(segment.substring(4, segment.length() - 2));
128+
}
129+
101130
/** @return Returns the last segment of the path */
102131
public String getLastSegment() {
103132
return segments.get(length() - 1);

0 commit comments

Comments
 (0)