Skip to content

Commit 92fc5cc

Browse files
authored
Make sync work across VV upgrades (#1913)
* Expose DataFile::_log() as public * Make sync work across VV upgrades The transition from tree-based revids to version vectors is tricky for replication, and the prior approach (adding a fake 'legacy' version when creating the vector) didn't turn out to work. Instead, when a doc is upgraded in memory to a VectorRecord it keeps its tree-based/legacy revids. When the doc is saved it creates a version vector for the local Revision but also remembers the prior legacy revid. Remote Revisions keep their tree-based IDs until VVs are pulled from or pushed to those remotes. Revision histories may now include one or more legacy revids at the end. These are what allow the recipient of a revision (an active or passive puller) to match up an incoming version vector with a local legacy revid. There's still a question of how long we need to keep the legacy revid of the document. It only needs to be used once per remote, but in a P2P scenario you don't know if you might have to sync with some new peer that hasn't upgraded that doc yet. In any case the revid isn't very big (~24 bytes) so it may not be worth removing. * Removed unnecessary VV-upgrade constants Some constants added earlier for VV upgrading and dealing with legacy revids, which have been superseded by newer techniques, were nuked: - kDocGetUpgraded / kUpgrade - kLegacyRevSourceID - VectorRecord::_versioning * Fixed some broken test code c4doc_getRevisionHistory requires doc be loaded with kDocGetAll. VectorDoc correctly threw an exception on this. Also, all this is doing is checking the current rev, so no need to get the history.
1 parent abe9759 commit 92fc5cc

26 files changed

+507
-512
lines changed

C/include/c4DocumentTypes.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ typedef C4_ENUM(uint8_t, C4DocContentLevel){
5151
kDocGetMetadata, ///< Only get revID and flags
5252
kDocGetCurrentRev, ///< Get current revision body but not other revisions/remotes
5353
kDocGetAll, ///< Get everything
54-
kDocGetUpgraded, ///< Get everything, upgrade to latest format (version vectors)
5554
}; // Note: Same as litecore::ContentOption
5655

5756
// Ignore warning about not initializing members, it must be this way to be C-compatible

C/tests/c4DatabaseTest.cc

Lines changed: 72 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,161 +1418,83 @@ N_WAY_TEST_CASE_METHOD(C4DatabaseTest, "Database Upgrade To Version Vectors", "[
14181418

14191419
auto defaultColl = c4db_getDefaultCollection(db, nullptr);
14201420

1421-
SECTION("Read-Only") {
1422-
// Check doc 1:
1423-
C4Document* doc;
1424-
doc = c4coll_getDoc(defaultColl, "doc-001"_sl, true, kDocGetAll, ERROR_INFO());
1425-
REQUIRE(doc);
1426-
CHECK(slice(doc->revID) == "2-c001d00d");
1427-
alloc_slice history(c4doc_getRevisionHistory(doc, 0, nullptr, 0));
1428-
CHECK(history == "2-c001d00d");
1429-
CHECK(doc->sequence == 7);
1430-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"one","rev":"two"})");
1431-
c4doc_release(doc);
1432-
1433-
// Check doc 2:
1434-
doc = c4coll_getDoc(defaultColl, "doc-002"_sl, true, kDocGetAll, ERROR_INFO());
1435-
REQUIRE(doc);
1436-
CHECK(slice(doc->revID) == "3-deadbeef");
1437-
history = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1438-
CHECK(history == "3-deadbeef");
1439-
CHECK(doc->sequence == 9);
1440-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"two","rev":"three"})");
1441-
alloc_slice remoteVers = c4doc_getRemoteAncestor(doc, 1);
1442-
CHECK(remoteVers == "3-deadbeef");
1443-
CHECK(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1444-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"two","rev":"three"})");
1445-
c4doc_release(doc);
1446-
1447-
// Check doc 3:
1448-
doc = c4coll_getDoc(defaultColl, "doc-003"_sl, true, kDocGetAll, ERROR_INFO());
1449-
REQUIRE(doc);
1450-
CHECK(slice(doc->revID) == "3-deadbeef");
1451-
history = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1452-
CHECK(history == "3-deadbeef, 2-c001d00d");
1453-
CHECK(doc->sequence == 11);
1454-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"three","rev":"three"})");
1455-
remoteVers = c4doc_getRemoteAncestor(doc, 1);
1456-
CHECK(remoteVers == "2-c001d00d");
1457-
CHECK(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1458-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"three","rev":"two"})");
1459-
c4doc_release(doc);
1460-
1461-
// Check doc 4:
1462-
doc = c4coll_getDoc(defaultColl, "doc-004"_sl, true, kDocGetAll, ERROR_INFO());
1463-
REQUIRE(doc);
1464-
CHECK(slice(doc->revID) == "3-deadbeef");
1465-
history = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1466-
CHECK(history == "3-deadbeef"); // parent 2-c001d00d doesn't show up bc it isn't a remote
1467-
CHECK(doc->sequence == 14);
1468-
CHECK(doc->flags == (kDocConflicted | kDocExists));
1469-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"four","rev":"three"})");
1470-
remoteVers = c4doc_getRemoteAncestor(doc, 1);
1471-
CHECK(remoteVers == "3-cc");
1472-
REQUIRE(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1473-
history = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1474-
CHECK(history == "3-cc");
1475-
CHECK(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1476-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"ans*wer":42})");
1477-
c4doc_release(doc);
1478-
1479-
// Check deleted doc:
1480-
doc = c4coll_getDoc(defaultColl, "doc-DEL"_sl, true, kDocGetAll, ERROR_INFO());
1481-
REQUIRE(doc);
1482-
CHECK(slice(doc->revID) == "1-abcd");
1483-
history = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1484-
CHECK(history == "1-abcd");
1485-
CHECK(doc->sequence == 6);
1486-
CHECK(doc->flags == (kDocDeleted | kDocExists));
1487-
c4doc_release(doc);
1488-
}
1489-
1490-
SECTION("Upgrading") {
1491-
// Note: The revID/version checks below hardcode the base timestamp used for upgrading legacy
1492-
// replicated revIDs. It's currently 0x1770000000000000 (see HybridClock.hh). If that value
1493-
// changes, or the scheme for converting rev-tree revIDs to versions changes, the values below
1494-
// need to change too.
1495-
REQUIRE(uint64_t(litecore::kMinValidTime) == 0x1770000000000000);
1496-
1497-
// Check doc 1:
1498-
C4Document* doc;
1499-
doc = c4coll_getDoc(defaultColl, "doc-001"_sl, true, kDocGetUpgraded, ERROR_INFO());
1500-
REQUIRE(doc);
1501-
CHECK(slice(doc->revID) == "1770000000000002@*");
1502-
alloc_slice versionVector(c4doc_getRevisionHistory(doc, 0, nullptr, 0));
1503-
CHECK(versionVector == "1770000000000002@*");
1504-
CHECK(doc->sequence == 7);
1505-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"one","rev":"two"})");
1506-
c4doc_release(doc);
1507-
1508-
// Check doc 2:
1509-
doc = c4coll_getDoc(defaultColl, "doc-002"_sl, true, kDocGetUpgraded, ERROR_INFO());
1510-
REQUIRE(doc);
1511-
CHECK(c4rev_getTimestamp(doc->revID) == uint64_t(litecore::kMinValidTime) + 3);
1512-
CHECK(slice(doc->revID) == "1770000000000003@?"); // 0x1770000000000003 = kMinValidTime + 3
1513-
versionVector = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1514-
CHECK(versionVector == "1770000000000003@?");
1515-
CHECK(doc->sequence == 9);
1516-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"two","rev":"three"})");
1517-
alloc_slice remoteVers = c4doc_getRemoteAncestor(doc, 1);
1518-
CHECK(remoteVers == "1770000000000003@?");
1519-
CHECK(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1520-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"two","rev":"three"})");
1421+
// Check doc 1:
1422+
C4Document* doc;
1423+
doc = c4coll_getDoc(defaultColl, "doc-001"_sl, true, kDocGetAll, ERROR_INFO());
1424+
REQUIRE(doc);
1425+
CHECK(slice(doc->revID) == "2-c001d00d");
1426+
alloc_slice history(c4doc_getRevisionHistory(doc, 0, nullptr, 0));
1427+
CHECK(history == "2-c001d00d");
1428+
CHECK(doc->sequence == 7);
1429+
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"one","rev":"two"})");
1430+
c4doc_release(doc);
15211431

1432+
// Check doc 2:
1433+
doc = c4coll_getDoc(defaultColl, "doc-002"_sl, true, kDocGetAll, ERROR_INFO());
1434+
REQUIRE(doc);
1435+
CHECK(slice(doc->revID) == "3-deadbeef");
1436+
history = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1437+
CHECK(history == "3-deadbeef");
1438+
CHECK(doc->sequence == 9);
1439+
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"two","rev":"three"})");
1440+
alloc_slice remoteVers = c4doc_getRemoteAncestor(doc, 1);
1441+
CHECK(remoteVers == "3-deadbeef");
1442+
CHECK(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1443+
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"two","rev":"three"})");
1444+
{
15221445
// update & save it:
1523-
{
1524-
TransactionHelper t(db);
1525-
C4Document* newDoc = c4doc_update(doc, kFleeceBody, 0, ERROR_INFO());
1526-
REQUIRE(newDoc);
1527-
CHECK(slice(newDoc->revID) > "178532e35bcd0001@*");
1528-
alloc_slice newVersionVector(c4doc_getRevisionHistory(newDoc, 0, nullptr, 0));
1529-
CHECK(newVersionVector.hasSuffix("@*; 1770000000000003@?"));
1530-
c4doc_release(newDoc);
1531-
}
1532-
c4doc_release(doc);
1446+
TransactionHelper t(db);
1447+
C4Document* newDoc = c4doc_update(doc, kFleeceBody, 0, ERROR_INFO());
1448+
REQUIRE(newDoc);
1449+
CHECK(slice(newDoc->revID).hasSuffix("@*"));
1450+
CHECK(slice(newDoc->revID) > "178532e35bcd0001@*");
1451+
alloc_slice newVersionVector(c4doc_getRevisionHistory(newDoc, 0, nullptr, 0));
1452+
CHECK(newVersionVector.hasSuffix("@*; 3-deadbeef"));
1453+
c4doc_release(newDoc);
1454+
}
1455+
c4doc_release(doc);
15331456

1534-
// Check doc 3:
1535-
doc = c4coll_getDoc(defaultColl, "doc-003"_sl, true, kDocGetUpgraded, ERROR_INFO());
1536-
REQUIRE(doc);
1537-
CHECK(slice(doc->revID) == "1770000000000003@*");
1538-
versionVector = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1539-
CHECK(versionVector == "1770000000000003@*; 1770000000000002@?");
1540-
CHECK(doc->sequence == 11);
1541-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"three","rev":"three"})");
1542-
remoteVers = c4doc_getRemoteAncestor(doc, 1);
1543-
CHECK(remoteVers == "1770000000000002@?");
1544-
CHECK(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1545-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"three","rev":"two"})");
1546-
c4doc_release(doc);
1457+
// Check doc 3:
1458+
doc = c4coll_getDoc(defaultColl, "doc-003"_sl, true, kDocGetAll, ERROR_INFO());
1459+
REQUIRE(doc);
1460+
CHECK(slice(doc->revID) == "3-deadbeef");
1461+
history = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1462+
CHECK(history == "3-deadbeef, 2-c001d00d");
1463+
CHECK(doc->sequence == 11);
1464+
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"three","rev":"three"})");
1465+
remoteVers = c4doc_getRemoteAncestor(doc, 1);
1466+
CHECK(remoteVers == "2-c001d00d");
1467+
CHECK(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1468+
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"three","rev":"two"})");
1469+
c4doc_release(doc);
15471470

1548-
// Check doc 4:
1549-
doc = c4coll_getDoc(defaultColl, "doc-004"_sl, true, kDocGetUpgraded, ERROR_INFO());
1550-
REQUIRE(doc);
1551-
CHECK(slice(doc->revID) == "1770000000000003@*");
1552-
versionVector = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1553-
CHECK(versionVector == "1770000000000003@*");
1554-
CHECK(doc->sequence == 14);
1555-
CHECK(doc->flags == (kDocConflicted | kDocExists));
1556-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"four","rev":"three"})");
1557-
remoteVers = c4doc_getRemoteAncestor(doc, 1);
1558-
CHECK(remoteVers == "1770000000000003@?");
1559-
REQUIRE(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1560-
versionVector = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1561-
CHECK(versionVector == "1770000000000003@?");
1562-
CHECK(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1563-
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"ans*wer":42})");
1564-
c4doc_release(doc);
1471+
// Check doc 4:
1472+
doc = c4coll_getDoc(defaultColl, "doc-004"_sl, true, kDocGetAll, ERROR_INFO());
1473+
REQUIRE(doc);
1474+
CHECK(slice(doc->revID) == "3-deadbeef");
1475+
history = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1476+
CHECK(history == "3-deadbeef"); // parent 2-c001d00d doesn't show up bc it isn't a remote
1477+
CHECK(doc->sequence == 14);
1478+
CHECK(doc->flags == (kDocConflicted | kDocExists));
1479+
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"doc":"four","rev":"three"})");
1480+
remoteVers = c4doc_getRemoteAncestor(doc, 1);
1481+
CHECK(remoteVers == "3-cc");
1482+
REQUIRE(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1483+
history = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1484+
CHECK(history == "3-cc");
1485+
CHECK(c4doc_selectRevision(doc, remoteVers, true, WITH_ERROR()));
1486+
CHECK(Dict(c4doc_getProperties(doc)).toJSONString() == R"({"ans*wer":42})");
1487+
c4doc_release(doc);
15651488

1566-
// Check deleted doc:
1567-
doc = c4coll_getDoc(defaultColl, "doc-DEL"_sl, true, kDocGetUpgraded, ERROR_INFO());
1568-
REQUIRE(doc);
1569-
CHECK(slice(doc->revID) == "1770000000000001@*");
1570-
versionVector = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1571-
CHECK(versionVector == "1770000000000001@*");
1572-
CHECK(doc->sequence == 6);
1573-
CHECK(doc->flags == (kDocDeleted | kDocExists));
1574-
c4doc_release(doc);
1575-
}
1489+
// Check deleted doc:
1490+
doc = c4coll_getDoc(defaultColl, "doc-DEL"_sl, true, kDocGetAll, ERROR_INFO());
1491+
REQUIRE(doc);
1492+
CHECK(slice(doc->revID) == "1-abcd");
1493+
history = c4doc_getRevisionHistory(doc, 0, nullptr, 0);
1494+
CHECK(history == "1-abcd");
1495+
CHECK(doc->sequence == 6);
1496+
CHECK(doc->flags == (kDocDeleted | kDocExists));
1497+
c4doc_release(doc);
15761498
}
15771499

15781500
// CBL-3706: Previously, calling these functions after deleting the default collection causes

C/tests/c4Test.cc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,15 @@ void C4Test::closeDB() {
248248
db = nullptr;
249249
}
250250

251-
void C4Test::reopenDB() {
252-
// Update _dbConfig in case db was reopened with different flags or encryption:
251+
void C4Test::syncDBConfig() {
252+
REQUIRE(db);
253253
_dbConfig.flags = c4db_getConfig2(db)->flags;
254254
_dbConfig.encryptionKey = c4db_getConfig2(db)->encryptionKey;
255+
}
256+
257+
void C4Test::reopenDB() {
258+
// Update _dbConfig in case db was reopened with different flags or encryption:
259+
syncDBConfig();
255260

256261
closeDB();
257262
db = c4db_openNamed(kDatabaseName, &_dbConfig, ERROR_INFO());

C/tests/c4Test.hh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ class C4Test {
217217

218218
[[nodiscard]] const C4DatabaseConfig2& dbConfig() const { return _dbConfig; }
219219

220+
void syncDBConfig();
221+
220222
[[nodiscard]] C4StorageEngine storageType() const { return _storage; }
221223

222224
[[nodiscard]] bool isSQLite() const { return storageType() == kC4SQLiteStorageEngine; }

0 commit comments

Comments
 (0)