Skip to content

Commit 5046288

Browse files
RCORE-2015 Merge rules for Clear instruction (#7597)
* Merge rules for Clear instructions using the collection type * Unit tests * Changes after code review * More changes after code review
1 parent fece6ff commit 5046288

File tree

4 files changed

+385
-59
lines changed

4 files changed

+385
-59
lines changed

src/realm/sync/changeset_parser.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,6 @@ void State::parse_one()
487487
Instruction::Clear instr;
488488
read_path_instr(instr);
489489
instr.collection_type = read_collection_type();
490-
// TODO: Add validation for collection_type once BAAS-29262 is done
491490
m_handler(instr);
492491
return;
493492
}

src/realm/sync/instruction_applier.cpp

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -877,87 +877,121 @@ void InstructionApplier::operator()(const Instruction::ArrayErase& instr)
877877

878878
void InstructionApplier::operator()(const Instruction::Clear& instr)
879879
{
880+
// For collections and nested collections in Mixed, applying a Clear instruction
881+
// implicitly sets the collection type (of the clear instruction) too.
880882
struct ClearResolver : public PathResolver {
881883
ClearResolver(InstructionApplier* applier, const Instruction::Clear& instr)
882884
: PathResolver(applier, instr, "Clear")
883885
{
886+
switch (instr.collection_type) {
887+
case Instruction::CollectionType::Single:
888+
break;
889+
case Instruction::CollectionType::List:
890+
m_collection_type = CollectionType::List;
891+
break;
892+
case Instruction::CollectionType::Dictionary:
893+
m_collection_type = CollectionType::Dictionary;
894+
break;
895+
case Instruction::CollectionType::Set:
896+
m_collection_type = CollectionType::Set;
897+
break;
898+
}
884899
}
885900
void on_list(LstBase& list) override
886901
{
902+
// list property
903+
if (m_collection_type && *m_collection_type != CollectionType::List) {
904+
m_applier->bad_transaction_log("Clear: Not a List");
905+
}
887906
list.clear();
888907
}
889908
Status on_list_index(LstBase& list, uint32_t index) override
890909
{
910+
REALM_ASSERT(m_collection_type);
891911
REALM_ASSERT(dynamic_cast<Lst<Mixed>*>(&list));
892912
auto& mixed_list = static_cast<Lst<Mixed>&>(list);
893913
if (index >= mixed_list.size()) {
894914
m_applier->bad_transaction_log("Clear: Index out of bounds (%1 > %2)", index,
895915
mixed_list.size()); // Throws
896-
return Status::DidNotResolve;
897916
}
898917
auto val = mixed_list.get(index);
899918
if (val.is_type(type_Dictionary)) {
900919
Dictionary d(mixed_list, mixed_list.get_key(index));
901920
d.clear();
902-
return Status::Pending;
903921
}
904-
if (val.is_type(type_List)) {
922+
else if (val.is_type(type_List)) {
905923
Lst<Mixed> l(mixed_list, mixed_list.get_key(index));
906924
l.clear();
907-
return Status::Pending;
908925
}
909-
m_applier->bad_transaction_log("Clear: Item (%1) at index %2 is not a collection", val.get_type(),
910-
index); // Throws
911-
return Status::DidNotResolve;
926+
else if (val.is_type(type_Set)) {
927+
m_applier->bad_transaction_log("Clear: Item at index %1 is a Set",
928+
index); // Throws
929+
}
930+
mixed_list.set_collection(size_t(index), *m_collection_type);
931+
return Status::Pending;
912932
}
913933
void on_dictionary(Dictionary& dict) override
914934
{
935+
// dictionary property
936+
if (m_collection_type && *m_collection_type != CollectionType::Dictionary) {
937+
m_applier->bad_transaction_log("Clear: Not a Dictionary");
938+
}
915939
dict.clear();
916940
}
917941
Status on_dictionary_key(Dictionary& dict, Mixed key) override
918942
{
943+
REALM_ASSERT(m_collection_type);
919944
auto val = dict.get(key);
920945
if (val.is_type(type_Dictionary)) {
921946
Dictionary d(dict, dict.build_index(key));
922947
d.clear();
923-
return Status::Pending;
924948
}
925-
if (val.is_type(type_List)) {
949+
else if (val.is_type(type_List)) {
926950
Lst<Mixed> l(dict, dict.build_index(key));
927951
l.clear();
928-
return Status::Pending;
929952
}
930-
m_applier->bad_transaction_log("Clear: Item (%1) at key '%2' is not a collection", val.get_type(),
931-
key); // Throws
932-
return Status::DidNotResolve;
953+
else if (val.is_type(type_Set)) {
954+
m_applier->bad_transaction_log("Clear: Item at key '%1' is a Set",
955+
key); // Throws
956+
}
957+
dict.insert_collection(key.get_string(), *m_collection_type);
958+
return Status::Pending;
933959
}
934960
void on_set(SetBase& set) override
935961
{
962+
// set property
963+
if (m_collection_type && *m_collection_type != CollectionType::Set) {
964+
m_applier->bad_transaction_log("Clear: Not a Set");
965+
}
936966
set.clear();
937967
}
938968
void on_property(Obj& obj, ColKey col_key) override
939969
{
940970
if (col_key.get_type() == col_type_Mixed) {
971+
REALM_ASSERT(m_collection_type);
941972
auto val = obj.get<Mixed>(col_key);
942973
if (val.is_type(type_Dictionary)) {
943974
Dictionary dict(obj, col_key);
944975
dict.clear();
945-
return;
946976
}
947977
else if (val.is_type(type_List)) {
948978
Lst<Mixed> list(obj, col_key);
949979
list.clear();
950-
return;
951980
}
952981
else if (val.is_type(type_Set)) {
953-
Set<Mixed> set(obj, col_key);
954-
set.clear();
955-
return;
982+
m_applier->bad_transaction_log("Clear: Mixed property is a Set"); // Throws
956983
}
984+
obj.set_collection(col_key, *m_collection_type);
985+
return;
957986
}
958987

959988
PathResolver::on_property(obj, col_key);
960989
}
990+
991+
private:
992+
// The server may not send the type for collection properties (non-Mixed)
993+
// since the clients don't send it either before v13.
994+
std::optional<CollectionType> m_collection_type;
961995
};
962996
ClearResolver(this, instr).resolve();
963997
}

src/realm/sync/transform.cpp

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,7 +1466,7 @@ DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseObject);
14661466
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseObject);
14671467

14681468

1469-
/// Set rules
1469+
/// Update rules
14701470

14711471
DEFINE_NESTED_MERGE(Instruction::Update)
14721472
{
@@ -1695,14 +1695,33 @@ DEFINE_MERGE(Instruction::ArrayErase, Instruction::Update)
16951695
DEFINE_MERGE(Instruction::Clear, Instruction::Update)
16961696
{
16971697
using Type = Instruction::Payload::Type;
1698+
using CollectionType = Instruction::CollectionType;
16981699

16991700
// The two instructions are at the same level of nesting.
17001701
if (same_path(left, right)) {
1701-
// TODO: We could make it so a Clear instruction does not win against setting a property or
1702-
// collection item to a different collection.
1703-
if (right.value.type != Type::List && right.value.type != Type::Dictionary) {
1702+
REALM_ASSERT(right.value.type != Type::Set);
1703+
// If both sides are setting/operating on the same type, let them both pass through.
1704+
// It is important that the instruction application rules reflect this.
1705+
// If it is not two lists or dictionaries, then the normal last-writer-wins rules will take effect below.
1706+
if (left.collection_type == CollectionType::List && right.value.type == Type::List) {
1707+
return;
1708+
}
1709+
if (left.collection_type == CollectionType::Dictionary && right.value.type == Type::Dictionary) {
1710+
return;
1711+
}
1712+
1713+
// CONFLICT: Clear and Update of the same element.
1714+
//
1715+
// RESOLUTION: Discard the instruction with the lower timestamp. This has the
1716+
// effect of preserving insertions that came after the clear (if it has the
1717+
// higher timestamp), or preserve additional updates (and potential insertions)
1718+
// that came after the update.
1719+
if (left_side.timestamp() < right_side.timestamp()) {
17041720
left_side.discard();
17051721
}
1722+
else {
1723+
right_side.discard();
1724+
}
17061725
}
17071726
}
17081727

@@ -1720,13 +1739,20 @@ DEFINE_NESTED_MERGE(Instruction::AddInteger)
17201739
}
17211740
}
17221741

1742+
DEFINE_MERGE(Instruction::Clear, Instruction::AddInteger)
1743+
{
1744+
// The two instructions are at the same level of nesting.
1745+
if (same_path(left, right)) {
1746+
right_side.discard();
1747+
}
1748+
}
1749+
17231750
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::AddInteger);
17241751
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::AddInteger);
17251752
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::AddInteger);
17261753
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddInteger);
17271754
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddInteger);
17281755
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddInteger);
1729-
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddInteger);
17301756
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddInteger);
17311757
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddInteger);
17321758

0 commit comments

Comments
 (0)