1 /*************************************************************************
3 * Copyright 2017 Realm Inc.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 **************************************************************************/
19 #ifndef REALM_SYNC_OBJECT_ID_HPP
20 #define REALM_SYNC_OBJECT_ID_HPP
22 #include <functional> // std::hash
24 #include <iosfwd> // operator<<
30 #include <realm/util/optional.hpp>
31 #include <realm/string_data.hpp>
32 #include <realm/data_type.hpp>
34 // Only set this to one when testing the code paths that exercise object ID
35 // hash collisions. It artificially limits the "optimistic" local ID to use
36 // only the lower 15 bits of the ID rather than the lower 63 bits, making it
37 // feasible to generate collisions within reasonable time.
38 #define REALM_EXERCISE_OBJECT_ID_COLLISION 0
46 /// ObjectIDs are globally unique, and up to 128 bits wide. They are represented
47 /// as two 64-bit integers, each of which may frequently be small, for best
48 /// on-wire compressibility.
50 constexpr ObjectID(uint64_t hi, uint64_t lo);
51 static ObjectID from_string(StringData);
53 // FIXME: Remove "empty" ObjectIDs, wrap in Optional instead.
54 constexpr ObjectID(realm::util::None = realm::util::none);
55 constexpr ObjectID(const ObjectID&) noexcept = default;
56 ObjectID& operator=(const ObjectID&) noexcept = default;
58 constexpr uint64_t lo() const { return m_lo; }
59 constexpr uint64_t hi() const { return m_hi; }
61 std::string to_string() const;
63 constexpr bool operator<(const ObjectID& other) const;
64 constexpr bool operator==(const ObjectID& other) const;
65 constexpr bool operator!=(const ObjectID& other) const;
72 /// Implementors of this interface should define a way to map from 128-bit
73 /// on-write ObjectIDs to local 64-bit object IDs.
75 /// The three object ID types are:
76 /// a. Object IDs for objects in tables without primary keys.
77 /// b. Object IDs for objects in tables with integer primary keys.
78 /// c. Object IDs for objects in tables with other primary key types.
80 /// For integer primary keys (b), the Object ID is just the integer value.
82 /// For objects without primary keys (a), a "squeezed" tuple of the
83 /// client_file_ident and a peer-local sequence number is used as the local
84 /// Object ID. The on-write Object ID is the "unsqueezed" format. The methods on
85 /// this interface ending in "_squeezed" aid in the creation and conversion of
88 /// For objects with other types of primary keys (c), the ObjectID
89 /// is a 128-bit hash of the primary key value. However, the local object ID
90 /// must be a 64-bit integer, because that is the maximum size integer that
91 /// Realm is able to store. The solution is to optimistically use the lower 63
92 /// bits of the on-wire Object ID, and use a local ID with the upper 64th bit
93 /// set when there is a collision in the lower 63 bits between two different
95 class ObjectIDProvider {
97 using LocalObjectID = int_fast64_t;
99 /// Calculate optimistic local ID that may collide with others. It is up to
100 /// the caller to ensure that collisions are detected and that
101 /// allocate_local_id_after_collision() is called to obtain a non-colliding
103 static LocalObjectID get_optimistic_local_id_hashed(ObjectID global_id);
105 /// Find the local 64-bit object ID for the provided global 128-bit ID.
106 virtual LocalObjectID global_to_local_object_id_hashed(size_t table_ndx, ObjectID global_id) const = 0;
108 /// After a local ID collision has been detected, this function may be
109 /// called to obtain a non-colliding local ID in such a way that subsequence
110 /// calls to global_to_local_object_id() will return the correct local ID
111 /// for both \a incoming_id and \a colliding_id.
112 virtual LocalObjectID allocate_local_id_after_hash_collision(size_t table_ndx,
113 ObjectID incoming_id,
114 ObjectID colliding_id,
115 LocalObjectID colliding_local_id) = 0;
116 static LocalObjectID make_tagged_local_id_after_hash_collision(uint64_t sequence_number);
117 virtual void free_local_id_after_hash_collision(size_t table_ndx, ObjectID object_id) = 0;
119 /// Some Object IDs are generated as a tuple of the client_file_ident and a
120 /// local sequence number. This function takes the next number in the
121 /// sequence for the given table and returns an appropriate globally unique
123 virtual ObjectID allocate_object_id_squeezed(size_t table_ndx) = 0;
124 static LocalObjectID global_to_local_object_id_squeezed(ObjectID);
125 static ObjectID local_to_global_object_id_squeezed(LocalObjectID);
127 virtual int_fast64_t get_client_file_ident() const = 0;
130 // ObjectIDSet is a set of (table name, object id)
134 void insert(StringData table, ObjectID object_id);
135 void erase(StringData table, ObjectID object_id);
136 bool contains(StringData table, ObjectID object_id) const noexcept;
140 // A map from table name to a set of object ids.
141 std::map<std::string, std::set<ObjectID>, std::less<>> m_objects;
147 constexpr ObjectID::ObjectID(uint64_t hi, uint64_t lo): m_lo(lo), m_hi(hi)
151 constexpr ObjectID::ObjectID(realm::util::None): m_lo(-1), m_hi(-1)
155 constexpr bool ObjectID::operator<(const ObjectID& other) const
157 return (m_hi == other.m_hi) ? (m_lo < other.m_lo) : (m_hi < other.m_hi);
160 constexpr bool ObjectID::operator==(const ObjectID& other) const
162 return m_hi == other.m_hi && m_lo == other.m_lo;
165 constexpr bool ObjectID::operator!=(const ObjectID& other) const
167 return !(*this == other);
170 std::ostream& operator<<(std::ostream&, const realm::sync::ObjectID&);
172 inline ObjectIDProvider::LocalObjectID
173 ObjectIDProvider::get_optimistic_local_id_hashed(ObjectID global_id)
175 #if REALM_EXERCISE_OBJECT_ID_COLLISION
176 const uint64_t optimistic_mask = 0xff;
178 const uint64_t optimistic_mask = 0x7fffffffffffffff;
180 static_assert(optimistic_mask < 0x8000000000000000, "optimistic Object ID mask must leave the 64th bit zero");
181 return global_id.lo() & optimistic_mask;
184 inline ObjectIDProvider::LocalObjectID
185 ObjectIDProvider::make_tagged_local_id_after_hash_collision(uint64_t sequence_number)
187 REALM_ASSERT(sequence_number < 0x8000000000000000);
188 return 0x8000000000000000 | sequence_number;
191 inline ObjectIDProvider::LocalObjectID
192 ObjectIDProvider::global_to_local_object_id_squeezed(ObjectID object_id)
194 REALM_ASSERT(object_id.hi() <= std::numeric_limits<uint32_t>::max());
195 REALM_ASSERT(object_id.lo() <= std::numeric_limits<uint32_t>::max());
197 uint64_t a = object_id.lo() & 0xff;
198 uint64_t b = (object_id.hi() & 0xff) << 8;
199 uint64_t c = (object_id.lo() & 0xffffff00) << 8;
200 uint64_t d = (object_id.hi() & 0xffffff00) << 32;
205 bitcast.u = a | b | c | d;
210 ObjectIDProvider::local_to_global_object_id_squeezed(LocalObjectID squeezed)
216 bitcast.s = squeezed;
218 uint64_t u = bitcast.u;
220 uint64_t lo = (u & 0xff) | ((u & 0xffffff0000) >> 8);
221 uint64_t hi = ((u & 0xff00) >> 8) | ((u & 0xffffff0000000000) >> 32);
222 return ObjectID{hi, lo};
231 struct hash<realm::sync::ObjectID> {
232 size_t operator()(realm::sync::ObjectID oid) const
234 return std::hash<uint64_t>{}(oid.lo()) ^ std::hash<uint64_t>{}(oid.hi());
240 #endif // REALM_SYNC_OBJECT_ID_HPP