diff --git a/CHANGELOG.md b/CHANGELOG.md index 19627784a7..175e46f085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) ---------------------------------------------- diff --git a/dependencies.yml b/dependencies.yml index b309513d2a..3a0549fdfa 100644 --- a/dependencies.yml +++ b/dependencies.yml @@ -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 diff --git a/src/realm/db.hpp b/src/realm/db.hpp index 72db6a2a5c..eb468372ce 100644 --- a/src/realm/db.hpp +++ b/src/realm/db.hpp @@ -295,6 +295,12 @@ class DB : public std::enable_shared_from_this { 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 diff --git a/src/realm/error_codes.cpp b/src/realm/error_codes.cpp index d137afc28f..b2427bf4bc 100644 --- a/src/realm/error_codes.cpp +++ b/src/realm/error_codes.cpp @@ -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: @@ -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}, diff --git a/src/realm/error_codes.h b/src/realm/error_codes.h index 4b0a85bcd8..3edbe9cd5d 100644 --- a/src/realm/error_codes.h +++ b/src/realm/error_codes.h @@ -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, @@ -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; diff --git a/src/realm/error_codes.hpp b/src/realm/error_codes.hpp index 1d85db0d1c..a61e7d2925 100644 --- a/src/realm/error_codes.hpp +++ b/src/realm/error_codes.hpp @@ -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, diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index eb57eafd45..8d9f6c4278 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -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()) { diff --git a/src/realm/sync/protocol.cpp b/src/realm/sync/protocol.cpp index 525604f785..b4f57cb2ba 100644 --- a/src/realm/sync/protocol.cpp +++ b/src/realm/sync/protocol.cpp @@ -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; } @@ -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]]; diff --git a/src/realm/sync/protocol.hpp b/src/realm/sync/protocol.hpp index eab452bdf2..c5aa6666a7 100644 --- a/src/realm/sync/protocol.hpp +++ b/src/realm/sync/protocol.hpp @@ -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 }; diff --git a/test/object-store/CMakeLists.txt b/test/object-store/CMakeLists.txt index b39deae4ae..8bfa4826ff 100644 --- a/test/object-store/CMakeLists.txt +++ b/test/object-store/CMakeLists.txt @@ -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 diff --git a/test/object-store/sync/flx_relaxed_schema.cpp b/test/object-store/sync/flx_relaxed_schema.cpp new file mode 100644 index 0000000000..ae368e5c74 --- /dev/null +++ b/test/object-store/sync/flx_relaxed_schema.cpp @@ -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 + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +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 diff --git a/test/object-store/sync/flx_schema_migration.cpp b/test/object-store/sync/flx_schema_migration.cpp index 1058cadf01..2915331a58 100644 --- a/test/object-store/sync/flx_schema_migration.cpp +++ b/test/object-store/sync/flx_schema_migration.cpp @@ -223,16 +223,6 @@ void check_realm_schema(const std::string& path, const std::vector } } -auto make_error_handler() -{ - auto [error_promise, error_future] = util::make_promise_future(); - auto shared_promise = std::make_shared(std::move(error_promise)); - auto fn = [error_promise = std::move(shared_promise)](std::shared_ptr, 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]") { diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index 9f648c44d7..acce02c54c 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -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(); - auto shared_promise = std::make_shared(std::move(error_promise)); - auto fn = [error_promise = std::move(shared_promise)](std::shared_ptr, 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 harness{"error_handling"}; create_user_and_log_in(harness->app()); @@ -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(); - auto shared_promise = std::make_shared(std::move(error_promise)); - auto fn = [error_promise = std::move(shared_promise)](std::shared_ptr, 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); diff --git a/test/object-store/util/sync/sync_test_utils.cpp b/test/object-store/util/sync/sync_test_utils.cpp index 80191b355b..08e02f7830 100644 --- a/test/object-store/util/sync/sync_test_utils.cpp +++ b/test/object-store/util/sync/sync_test_utils.cpp @@ -228,6 +228,25 @@ std::string get_compile_time_admin_url() return {}; #endif // REALM_ADMIN_ENDPOINT } + +std::pair, std::function, SyncError err)>> +make_error_handler() +{ + auto [error_promise, error_future] = util::make_promise_future(); + auto shared_promise = std::make_shared(std::move(error_promise)); + auto fn = [error_promise = std::move(shared_promise)](std::shared_ptr, 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 diff --git a/test/object-store/util/sync/sync_test_utils.hpp b/test/object-store/util/sync/sync_test_utils.hpp index b4c5637ee3..0932c6e52b 100644 --- a/test/object-store/util/sync/sync_test_utils.hpp +++ b/test/object-store/util/sync/sync_test_utils.hpp @@ -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, std::function, SyncError err)>> +make_error_handler(); + #endif // REALM_ENABLE_AUTH_TESTS void wait_for_advance(Realm& realm); diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 57dd1d11c6..21a6593dfe 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -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(); @@ -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();