Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RCORE-2236 Add support for relaxed schema in bind message #7981

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
-----------

### Internals
* None.
* Added support for relaxed schema errors and added the `relaxedSchema` value to the BIND json data. ([PR #7981](https://github.com/realm/realm-core/pull/7981))

----------------------------------------------

Expand Down
4 changes: 2 additions & 2 deletions dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ VERSION: 14.12.0
OPENSSL_VERSION: 3.3.1
ZLIB_VERSION: 1.2.13
# https://github.com/10gen/baas/commits
# 2f308db is 2024 July 10
BAAS_VERSION: 2f308db6f65333728a101d1fecbb792f9659a5ce
# 04e3f27 is 2024 August 9
BAAS_VERSION: 04e3f27ad0eb9154bc4e3b631d179d702ac05215
BAAS_VERSION_TYPE: githash
6 changes: 6 additions & 0 deletions src/realm/db.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,12 @@ class DB : public std::enable_shared_from_this<DB> {
return m_evac_stage;
}

// Has the relaxed schema feature been enabled for this realm file?
bool flexible_schema_allowed() const
{
return m_allow_flexible_schema;
}

/// Report the number of distinct versions stored in the database at the time
/// of latest commit.
/// Note: the database only cleans up versions as part of commit, so ending
Expand Down
2 changes: 2 additions & 0 deletions src/realm/error_codes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ ErrorCategory ErrorCodes::error_categories(Error code)
case SyncWriteNotAllowed:
case SyncLocalClockBeforeEpoch:
case SyncSchemaMigrationError:
case SyncRelaxedSchemaError:
return ErrorCategory().set(ErrorCategory::runtime_error).set(ErrorCategory::sync_error);

case SyncConnectFailed:
Expand Down Expand Up @@ -396,6 +397,7 @@ static const constexpr MapElem string_to_error_code[] = {
{"SyncPermissionDenied", ErrorCodes::SyncPermissionDenied},
{"SyncProtocolInvariantFailed", ErrorCodes::SyncProtocolInvariantFailed},
{"SyncProtocolNegotiationFailed", ErrorCodes::SyncProtocolNegotiationFailed},
{"SyncRelaxedSchemaError", ErrorCodes::SyncRelaxedSchemaError},
{"SyncServerPermissionsChanged", ErrorCodes::SyncServerPermissionsChanged},
{"SyncUserMismatch", ErrorCodes::SyncUserMismatch},
{"SyncWriteNotAllowed", ErrorCodes::SyncWriteNotAllowed},
Expand Down
4 changes: 4 additions & 0 deletions src/realm/error_codes.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ typedef enum realm_errno {
RLM_ERR_SYNC_WRITE_NOT_ALLOWED = 1044,
RLM_ERR_SYNC_LOCAL_CLOCK_BEFORE_EPOCH = 1045,
RLM_ERR_SYNC_SCHEMA_MIGRATION_ERROR = 1046,
RLM_ERR_SYNC_RELAXED_SCHEMA_ERROR = 1047,

RLM_ERR_SYSTEM_ERROR = 1999,

Expand Down Expand Up @@ -266,6 +267,9 @@ typedef enum realm_sync_errno_session {
RLM_SYNC_ERR_SESSION_REVERT_TO_PBS = 234,
RLM_SYNC_ERR_SESSION_BAD_SCHEMA_VERSION = 235,
RLM_SYNC_ERR_SESSION_SCHEMA_VERSION_CHANGED = 236,

RLM_SYNC_ERR_SESSION_RELAXED_SCHEMA_MODE_CHANGED = 240,
RLM_SYNC_ERR_SESSION_RELAXED_SCHEMA_NOT_SUPPORTED = 241,
// Error code 299 is reserved as an "unknown session error" in tests
} realm_sync_errno_session_e;

Expand Down
1 change: 1 addition & 0 deletions src/realm/error_codes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class ErrorCodes {
SyncWriteNotAllowed = RLM_ERR_SYNC_WRITE_NOT_ALLOWED,
SyncLocalClockBeforeEpoch = RLM_ERR_SYNC_LOCAL_CLOCK_BEFORE_EPOCH,
SyncSchemaMigrationError = RLM_ERR_SYNC_SCHEMA_MIGRATION_ERROR,
SyncRelaxedSchemaError = RLM_ERR_SYNC_RELAXED_SCHEMA_ERROR,

SystemError = RLM_ERR_SYSTEM_ERROR,

Expand Down
3 changes: 3 additions & 0 deletions src/realm/sync/noinst/client_impl_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1906,6 +1906,9 @@ void Session::send_bind_message()
auto schema_version = get_schema_version();
// Send 0 if schema is not versioned.
bind_json_data["schemaVersion"] = schema_version != uint64_t(-1) ? schema_version : 0;
// Send false if sync client is not using a relaxed schema
/// TODO: decide if this should be an int instead of a bool
bind_json_data["relaxedSchema"] = get_db()->flexible_schema_allowed() ? true : false;
if (logger.would_log(util::Logger::Level::debug)) {
std::string json_data_dump;
if (!bind_json_data.empty()) {
Expand Down
10 changes: 10 additions & 0 deletions src/realm/sync/protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ const char* get_protocol_error_message(int error_code) noexcept
case ProtocolError::schema_version_changed:
return "Client opened a session with a new valid schema version - migrating client to use new schema "
"version (BIND)";
case ProtocolError::relaxed_schema_mode_changed:
return "Client attempted to change their relaxed schema mode for an existing client file - "
"requires client reset";
case ProtocolError::relaxed_schema_not_suppored:
return "Client using relaxed schema mode attempted to connect to an app that does not support "
"relaxed schema";
}
return nullptr;
}
Expand Down Expand Up @@ -223,6 +229,10 @@ Status protocol_error_to_status(ProtocolError error_code, std::string_view msg)
[[fallthrough]];
case ProtocolError::schema_version_changed:
return ErrorCodes::SyncSchemaMigrationError;
case ProtocolError::relaxed_schema_mode_changed:
[[fallthrough]];
case ProtocolError::relaxed_schema_not_suppored:
return ErrorCodes::SyncRelaxedSchemaError;

case ProtocolError::limits_exceeded:
[[fallthrough]];
Expand Down
2 changes: 2 additions & 0 deletions src/realm/sync/protocol.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ enum class ProtocolError {
revert_to_pbs = RLM_SYNC_ERR_SESSION_REVERT_TO_PBS, // Server rolled back to PBS after FLX migration - revert FLX client migration (BIND)
bad_schema_version = RLM_SYNC_ERR_SESSION_BAD_SCHEMA_VERSION, // Client tried to open a session with an invalid schema version (BIND)
schema_version_changed = RLM_SYNC_ERR_SESSION_SCHEMA_VERSION_CHANGED, // Client opened a session with a new valid schema version - migrate client to use new schema version (BIND)
relaxed_schema_mode_changed = RLM_SYNC_ERR_SESSION_RELAXED_SCHEMA_MODE_CHANGED, // Client attempted to change their relaxed schema mode for an existing client file - requires client reset (IDENT)
relaxed_schema_not_suppored = RLM_SYNC_ERR_SESSION_RELAXED_SCHEMA_NOT_SUPPORTED, // Client attempted to connect to an app that does not support relaxed schema with a client that is using relaxed schema (BIND)

// clang-format on
};
Expand Down
1 change: 1 addition & 0 deletions test/object-store/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ if(REALM_ENABLE_SYNC)
sync/client_reset.cpp
sync/flx_migration.cpp
sync/flx_role_change.cpp
sync/flx_relaxed_schema.cpp
sync/flx_schema_migration.cpp
sync/flx_sync.cpp
sync/metadata.cpp
Expand Down
93 changes: 93 additions & 0 deletions test/object-store/sync/flx_relaxed_schema.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

#if REALM_ENABLE_AUTH_TESTS

#include <catch2/catch_all.hpp>

#include <util/test_file.hpp>
#include <util/crypt_key.hpp>
#include <util/sync/flx_sync_harness.hpp>
#include <util/sync/sync_test_utils.hpp>

#include <realm/object_id.hpp>
#include <realm/query_expression.hpp>

#include <realm/object-store/binding_context.hpp>
#include <realm/object-store/impl/object_accessor_impl.hpp>
#include <realm/object-store/impl/realm_coordinator.hpp>
#include <realm/object-store/schema.hpp>
#include <realm/object-store/sync/generic_network_transport.hpp>
#include <realm/object-store/sync/mongo_client.hpp>
#include <realm/object-store/sync/mongo_database.hpp>
#include <realm/object-store/sync/mongo_collection.hpp>
#include <realm/object-store/sync/async_open_task.hpp>
#include <realm/util/bson/bson.hpp>
#include <realm/object-store/sync/sync_session.hpp>

#include <realm/sync/client_base.hpp>
#include <realm/sync/config.hpp>
#include <realm/sync/noinst/client_history_impl.hpp>
#include <realm/sync/noinst/client_reset.hpp>
#include <realm/sync/noinst/client_reset_operation.hpp>
#include <realm/sync/noinst/pending_bootstrap_store.hpp>
#include <realm/sync/noinst/server/access_token.hpp>
#include <realm/sync/protocol.hpp>
#include <realm/sync/subscriptions.hpp>

#include <realm/util/future.hpp>
#include <realm/util/logger.hpp>

#include <filesystem>
#include <iostream>
#include <stdexcept>

namespace realm {

class TestHelper {
public:
static DBRef& get_db(SharedRealm const& shared_realm)
{
return Realm::Internal::get_db(*shared_realm);
}
};

} // namespace realm

namespace realm::app {

TEST_CASE("flx: relaxed schema disabled in app returns error", "[sync][flx][relaxed schema][baas]") {
// Relaxed schema feature is disabled by default
FLXSyncTestHarness harness("relaxed-schema-disabled");

auto config = harness.make_test_file();
auto&& [error_future, error_handler] = make_error_handler();

config.flexible_schema = true;
config.sync_config->error_handler = error_handler;

auto realm = Realm::get_shared_realm(config);
auto error = wait_for_future(std::move(error_future)).get_no_throw();
REQUIRE(error.is_ok());
REQUIRE_FALSE(error.get_value().is_fatal);
REQUIRE(error.get_value().status == ErrorCodes::SyncRelaxedSchemaError);
}

} // namespace realm::app

#endif // REALM_ENABLE_AUTH_TESTS
10 changes: 0 additions & 10 deletions test/object-store/sync/flx_schema_migration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,16 +223,6 @@ void check_realm_schema(const std::string& path, const std::vector<ObjectSchema>
}
}

auto make_error_handler()
{
auto [error_promise, error_future] = util::make_promise_future<SyncError>();
auto shared_promise = std::make_shared<decltype(error_promise)>(std::move(error_promise));
auto fn = [error_promise = std::move(shared_promise)](std::shared_ptr<SyncSession>, SyncError err) {
error_promise->emplace_value(std::move(err));
};
return std::make_pair(std::move(error_future), std::move(fn));
}

} // namespace

TEST_CASE("Sync schema migrations don't work with sync open", "[sync][flx][flx schema migration][baas]") {
Expand Down
27 changes: 0 additions & 27 deletions test/object-store/sync/flx_sync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,16 +255,6 @@ TEST_CASE("flx: test commands work", "[sync][flx][test command][baas]") {
});
}

static auto make_error_handler()
{
auto [error_promise, error_future] = util::make_promise_future<SyncError>();
auto shared_promise = std::make_shared<decltype(error_promise)>(std::move(error_promise));
auto fn = [error_promise = std::move(shared_promise)](std::shared_ptr<SyncSession>, SyncError err) {
error_promise->emplace_value(std::move(err));
};
return std::make_pair(std::move(error_future), std::move(fn));
}

TEST_CASE("app: error handling integration test", "[sync][flx][baas]") {
static std::optional<FLXSyncTestHarness> harness{"error_handling"};
create_user_and_log_in(harness->app());
Expand Down Expand Up @@ -1631,23 +1621,6 @@ TEST_CASE("flx: uploading an object that is out-of-view results in compensating
create_user_and_log_in(harness->app());
auto user = harness->app()->current_user();

auto make_error_handler = [] {
auto [error_promise, error_future] = util::make_promise_future<SyncError>();
auto shared_promise = std::make_shared<decltype(error_promise)>(std::move(error_promise));
auto fn = [error_promise = std::move(shared_promise)](std::shared_ptr<SyncSession>, SyncError err) mutable {
if (!error_promise) {
util::format(std::cerr,
"An unexpected sync error was caught by the default SyncTestFile handler: '%1'\n",
err.status);
abort();
}
error_promise->emplace_value(std::move(err));
error_promise.reset();
};

return std::make_pair(std::move(error_future), std::move(fn));
};

auto validate_sync_error = [&](const SyncError& sync_error, Mixed expected_pk, const char* expected_object_name,
const std::string& error_msg_fragment) {
CHECK(sync_error.status == ErrorCodes::SyncCompensatingWrite);
Expand Down
19 changes: 19 additions & 0 deletions test/object-store/util/sync/sync_test_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,25 @@ std::string get_compile_time_admin_url()
return {};
#endif // REALM_ADMIN_ENDPOINT
}

std::pair<util::Future<SyncError>, std::function<void(std::shared_ptr<SyncSession>, SyncError err)>>
make_error_handler()
{
auto [error_promise, error_future] = util::make_promise_future<SyncError>();
auto shared_promise = std::make_shared<decltype(error_promise)>(std::move(error_promise));
auto fn = [error_promise = std::move(shared_promise)](std::shared_ptr<SyncSession>, SyncError err) mutable {
if (!error_promise) {
util::format(std::cerr, "An unexpected sync error was caught by the default SyncTestFile handler: '%1'\n",
err.status);
abort();
}
error_promise->emplace_value(std::move(err));
error_promise.reset();
};

return std::make_pair(std::move(error_future), std::move(fn));
}

#endif // REALM_ENABLE_AUTH_TESTS

#if REALM_APP_SERVICES
Expand Down
4 changes: 4 additions & 0 deletions test/object-store/util/sync/sync_test_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ void wait_for_sessions_to_close(const TestAppSession& test_app_session);

std::string get_compile_time_base_url();
std::string get_compile_time_admin_url();

std::pair<util::Future<SyncError>, std::function<void(std::shared_ptr<SyncSession>, SyncError err)>>
make_error_handler();

#endif // REALM_ENABLE_AUTH_TESTS

void wait_for_advance(Realm& realm);
Expand Down
8 changes: 8 additions & 0 deletions test/test_sync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5673,6 +5673,10 @@ TEST(Sync_Mixed)
auto db_1 = DB::create(make_client_replication(), db_1_path, options);
auto db_2 = DB::create(make_client_replication(), db_2_path, options);

// Flexible schema should be disabled by default
CHECK_NOT(db_1->flexible_schema_allowed());
CHECK_NOT(db_2->flexible_schema_allowed());

TEST_DIR(dir);
fixtures::ClientServerFixture fixture{dir, test_context};
fixture.start();
Expand Down Expand Up @@ -6232,6 +6236,10 @@ TEST(Sync_AdditionalProperties)
auto db_1 = DB::create(make_client_replication(), db_1_path, options);
auto db_2 = DB::create(make_client_replication(), db_2_path, options);

// Flexible schema is allowed
CHECK(db_1->flexible_schema_allowed());
CHECK(db_2->flexible_schema_allowed());

TEST_DIR(dir);
fixtures::ClientServerFixture fixture{dir, test_context};
fixture.start();
Expand Down
Loading