Write Data to a Synced Realm - C++ SDK
On this page
When writing data to a synced realm using Flexible Sync, you can use the same APIs as when performing CRUD operations on a non-synced realm. However, there are some differences in behavior to keep in mind as you develop your application.
When you write to a synced realm, your write operations must match both of the following:
The sync subscription query
The permissions in your App Services App
If you try to write data that doesn't match either the query subscription or the user's permissions, Realm reverts the write with a non-fatal error operation called a compensating write.
To learn more about configuring permissions for your app, see Role-based Permissions and the Device Sync Permissions Guide in the App Services documentation.
To learn more about permission denied errors, compensating write errors, and other Device Sync error types, refer to Sync Errors in the App Services documentation.
Determining What Data Syncs
The data that you can write to a synced realm is determined by the following:
Your Device Sync configuration
Permissions in your App
The Flexible Sync subscription query used when you open the realm
The examples on this page use an Atlas App Services App with the following Device Sync configuration and a client app with the following Realm SDK data model and subscriptions.
In this example, the client app uses the following object model:
struct Item { realm::primary_key<realm::object_id> _id{realm::object_id::generate()}; std::string ownerId; std::string itemName; int64_t complexity; }; REALM_SCHEMA(Item, _id, ownerId, itemName, complexity)
App Services Configuration
Based on the above example object model, Device Sync is configured with the following queryable fields:
_id
(always included)complexity
ownerId
The App Services App has permissions configured to let users read and write only their own data:
{ "roles": [ { "name": "readOwnWriteOwn", "apply_when": {}, "document_filters": { "write": { "ownerId": "%%user.id" }, "read": { "ownerId": "%%user.id" } }, "read": true, "write": true, "insert": true, "delete": true, "search": true } ] }
Any object in the Atlas collection where the ownerId
does not match
the user.identifier()
of the logged-in user cannot sync to this realm.
Client Data Model and Configuration
The examples on this page use the following synced realm configuration with this sync query and object model:
With this Sync query, any object in the Atlas collection where the
complexity
property's value is greater than 4
cannot sync to this
realm.
Write to a Synced Realm
Writes to Flexible Sync realms may broadly fall into one of two categories, depending on whether the write matches the permissions and the Flexible Sync subscription query:
Successful writes: The written object matches both the query subscription and the user's permissions. The object writes successfully to the realm, and syncs successfully to the App Services backend and other devices.
Compensating writes: The written object does not match the subscription query or the user does not have sufficient permissions to perform the write. Realm reverts the illegal write with a compensating write operation.
Tip
If you want to write an object that does not match the query subscription, you can open a different realm where the object matches the query subscription. Alternately, you could write the object to a non-synced realm that does not enforce permissions or subscription queries.
Successful Writes
When the write matches both user permissions and the query subscription in the client, the Realm C++ SDK can successfully write the object to the synced realm. This object syncs with the App Services backend when the device has a network connection.
// Per the Device Sync permissions, users can only read and write data // where the `Item.ownerId` property matches their own user ID. auto simpleItem = realm::Item{.ownerId = user.identifier(), .itemName = "This item meets sync criteria", .complexity = 3}; // `simpleItem` successfully writes to the realm and syncs to Atlas // because its data matches the subscription query (complexity <= 4) // and its `ownerId` field matches the user ID. syncRealm.write([&] { syncRealm.add(std::move(simpleItem)); });
Compensating Writes
When the write doesn't match either the query subscription or user permissions, Realm reverts the write and provides an array of compensating_write_error_info objects.
In more detail, when you write data that is outside the bounds of a query subscription or does not match the user's permissions, the following occurs:
Because the client realm has no concept of "illegal" writes, the write initially succeeds until Realm resolves the changeset with the App Services backend.
Upon sync, the server applies the rules and permissions. The server determines that the user does not have authorization to perform the write.
The server sends a revert operation, called a "compensating write", back to the client.
The client's realm reverts the illegal write operation.
Any client-side writes to a given object between an illegal write to that object and the corresponding compensating write will be lost. In practice, this may look like the write succeeding, but then the object "disappears" when Realm syncs with the App Services backend and performs the compensating write.
When this occurs, you can refer to the App Services logs
or use the compensating_writes_info()
function in the client to get
additional information on the error. For more information, refer
to the Compensating Write Error Information section on this page.
Write Doesn't Match the Query Subscription
Given the configuration for the Flexible Sync realm detailed above, attempting to write this object results in a compensating write error because the object does not match the query subscription:
// The complexity of this item is `7`. This is outside the bounds // of the subscription query, which triggers a compensating write. auto complexItem = realm::Item{._id = primaryKey, .ownerId = user.identifier(), .itemName = "Test compensating writes", .complexity = 7}; // This should trigger a compensating write error when it tries to sync // due to server-side permissions, which gets logged with the error handler. syncRealm.write([&] { syncRealm.add(std::move(complexItem)); });
Connection[2]: Session[10]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning)
You will see the following error message in the App Services logs:
Error: Client attempted a write that is not allowed; it has been reverted (ProtocolErrorCode=231) Details: { "Item": { "ObjectID(\"6557ddb0bf050934870ca0f5\")": "write to ObjectID(\"6557ddb0bf050934870ca0f5\") in table \"Item\" not allowed; object is outside of the current query view" } }
Write Doesn't Match Permissions
Given the permissions in the Device Sync configuration detailed above,
attempting to write this object results in a compensating write error because the ownerId
property does not match
the user.identifier()
of the logged-in user:
// The `ownerId` of this item does not match the user ID of the logged-in // user. The user does not have permissions to make this write, which // triggers a compensating write. auto itemWithWrongOwner = realm::Item{ .ownerId = "not the current user", .itemName = "Trigger an incorrect permissions compensating write", .complexity = 1}; syncRealm.write([&] { syncRealm.add(std::move(itemWithWrongOwner)); });
Connection[2]: Session[11]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning)
You will see the following error message in the App Services logs:
Error: Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231) Details: { "Item": { "ObjectID(\"6557ddbabf050934870ca0f8\")": "write to ObjectID(\"6557ddbabf050934870ca0f8\") in table \"Item\" was denied by write filter in role \"readOwnWriteOwn\"" } }
Compensating Write Error Information
You can get additional information in the client about why a compensating
write occurs using the compensating_writes_info()
function, which provides an array of compensating_write_error_info
structs that contain:
The
object_name
of the object the client attempted to writeThe
primary_key
of the specific objectThe
reason
for the compensating write error
This information is the same information you can find in the App Services logs. The C++ SDK exposes this object on the client for convenience and debugging purposes.
The following shows an example of how you might log information about compensating write errors:
auto info = receivedSyncError.compensating_writes_info(); for (auto &v : info) { std::cout << "A write was rejected with a compensating write error.\n"; std::cout << "An object of type " << v.object_name << "\n"; std::cout << "was rejected because " << v.reason << ".\n"; }
A write was rejected with a compensating write error. An object of type Item was rejected because write to ObjectID("6557ddb0bf050934870ca0f5") in table "Item" not allowed; object is outside of the current query view.
The
Item
in this message isItem
object used in the object model on this page.The
table "Item"
refers to the Atlas collection where this object would sync.The reason
object is outside of the current query view
in this message is because the query subscription was set to require the object'scomplexity
property to be less than or equal to4
. The client attempted to write an object outside of this boundary.The primary key is the
objectId
of the specific object the client attempted to write.
Group Writes for Improved Performance
Every write transaction for a subscription set has a performance cost. If you need to make multiple updates to a Realm object during a session, consider keeping edited objects in memory until all changes are complete. This improves sync performance by only writing the complete and updated object to your realm instead of every change.