From 2643323a0143b66338ba13d747aa2d2c27b98daf Mon Sep 17 00:00:00 2001 From: blagoev Date: Fri, 11 Aug 2023 19:58:36 +0300 Subject: [PATCH 01/34] 1 --- src/realm/object-store/c_api/error.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/realm/object-store/c_api/error.cpp b/src/realm/object-store/c_api/error.cpp index d2cd805d36b..abb93392bc1 100644 --- a/src/realm/object-store/c_api/error.cpp +++ b/src/realm/object-store/c_api/error.cpp @@ -165,6 +165,7 @@ bool ErrorStorage::clear() noexcept void ErrorStorage::set_usercode_error(void* usercode_error) { + printf("setting usercode_error ptr %p", usercode_error); m_usercode_error = usercode_error; } From a23014b23dc4b98f155d1770da1674459a47d3f6 Mon Sep 17 00:00:00 2001 From: blagoev Date: Fri, 11 Aug 2023 20:44:44 +0300 Subject: [PATCH 02/34] 123 --- src/realm/object-store/c_api/error.cpp | 9 +++++++++ src/realm/object-store/c_api/types.hpp | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/realm/object-store/c_api/error.cpp b/src/realm/object-store/c_api/error.cpp index abb93392bc1..cf9838c0e80 100644 --- a/src/realm/object-store/c_api/error.cpp +++ b/src/realm/object-store/c_api/error.cpp @@ -16,6 +16,7 @@ ErrorStorage::ErrorStorage(std::exception_ptr ptr) noexcept : m_err(none) , m_message_buf() { + printf("ErrorStorage::ErrorStorage(std::exception_ptr ptr) noexcept"); assign(std::move(ptr)); } @@ -23,6 +24,7 @@ ErrorStorage::ErrorStorage(const ErrorStorage& other) : m_err(other.m_err) , m_message_buf(other.m_message_buf) { + printf("ErrorStorage::ErrorStorage(const ErrorStorage& other)"); if (m_err) { m_err->message = m_message_buf.c_str(); } @@ -30,6 +32,7 @@ ErrorStorage::ErrorStorage(const ErrorStorage& other) ErrorStorage& ErrorStorage::operator=(const ErrorStorage& other) { + printf("ErrorStorage& ErrorStorage::operator=(const ErrorStorage& other)"); m_err = other.m_err; m_message_buf = other.m_message_buf; if (m_err) { @@ -42,6 +45,7 @@ ErrorStorage::ErrorStorage(ErrorStorage&& other) : m_err(std::move(other.m_err)) , m_message_buf(std::move(other.m_message_buf)) { + printf("ErrorStorage::ErrorStorage(ErrorStorage&& other)"); if (m_err) { m_err->message = m_message_buf.c_str(); } @@ -50,6 +54,7 @@ ErrorStorage::ErrorStorage(ErrorStorage&& other) ErrorStorage& ErrorStorage::operator=(ErrorStorage&& other) { + printf("ErrorStorage& ErrorStorage::operator=(ErrorStorage&& other)"); m_err = std::move(other.m_err); m_message_buf = std::move(other.m_message_buf); if (m_err) { @@ -72,6 +77,7 @@ bool ErrorStorage::operator==(const ErrorStorage& other) const noexcept void ErrorStorage::assign(std::exception_ptr eptr) noexcept { + printf("void ErrorStorage::assign(std::exception_ptr eptr) noexcept"); if (!eptr) { clear(); return; @@ -146,6 +152,7 @@ bool ErrorStorage::has_error() const noexcept bool ErrorStorage::get_as_realm_error_t(realm_error_t* out) const noexcept { + printf("bool ErrorStorage::get_as_realm_error_t(realm_error_t* out) const noexcept"); if (!m_err) { return false; } @@ -178,6 +185,7 @@ void* ErrorStorage::get_and_clear_usercode_error() ErrorStorage* ErrorStorage::get_thread_local() { + printf("ErrorStorage* ErrorStorage::get_thread_local()"); #if !defined(RLM_NO_THREAD_LOCAL) static thread_local ErrorStorage g_error_storage; return &g_error_storage; @@ -242,5 +250,6 @@ RLM_EXPORT bool realm_wrap_exceptions(void (*func)()) noexcept RLM_API void realm_register_user_code_callback_error(void* usercode_error) noexcept { + printf("RLM_API void realm_register_user_code_callback_error(void* usercode_error) noexcept"); realm::c_api::ErrorStorage::get_thread_local()->set_usercode_error(usercode_error); } diff --git a/src/realm/object-store/c_api/types.hpp b/src/realm/object-store/c_api/types.hpp index 13b8f3ac18d..92c501d6a3e 100644 --- a/src/realm/object-store/c_api/types.hpp +++ b/src/realm/object-store/c_api/types.hpp @@ -113,11 +113,13 @@ struct realm_async_error : realm::c_api::WrapC { explicit realm_async_error(const realm::c_api::ErrorStorage& storage) : error_storage(storage) { + printf("explicit realm_async_error(const realm::c_api::ErrorStorage& storage)\n"); } explicit realm_async_error(std::exception_ptr ep) : error_storage(std::move(ep)) { + printf("explicit realm_async_error(std::exception_ptr ep)"); } realm_async_error* clone() const override From 3dc310560f14b3ab4162a00fb4e83b4ebf1fc34f Mon Sep 17 00:00:00 2001 From: blagoev Date: Fri, 11 Aug 2023 21:02:07 +0300 Subject: [PATCH 03/34] 123 --- src/realm/object-store/c_api/error.cpp | 20 ++++++++++---------- src/realm/object-store/c_api/types.hpp | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/realm/object-store/c_api/error.cpp b/src/realm/object-store/c_api/error.cpp index cf9838c0e80..7fd6f9a3bc6 100644 --- a/src/realm/object-store/c_api/error.cpp +++ b/src/realm/object-store/c_api/error.cpp @@ -16,7 +16,7 @@ ErrorStorage::ErrorStorage(std::exception_ptr ptr) noexcept : m_err(none) , m_message_buf() { - printf("ErrorStorage::ErrorStorage(std::exception_ptr ptr) noexcept"); + printf("ErrorStorage::ErrorStorage(std::exception_ptr ptr) noexcept\n"); assign(std::move(ptr)); } @@ -24,7 +24,7 @@ ErrorStorage::ErrorStorage(const ErrorStorage& other) : m_err(other.m_err) , m_message_buf(other.m_message_buf) { - printf("ErrorStorage::ErrorStorage(const ErrorStorage& other)"); + printf("ErrorStorage::ErrorStorage(const ErrorStorage& other)\n"); if (m_err) { m_err->message = m_message_buf.c_str(); } @@ -32,7 +32,7 @@ ErrorStorage::ErrorStorage(const ErrorStorage& other) ErrorStorage& ErrorStorage::operator=(const ErrorStorage& other) { - printf("ErrorStorage& ErrorStorage::operator=(const ErrorStorage& other)"); + printf("ErrorStorage& ErrorStorage::operator=(const ErrorStorage& other)\n"); m_err = other.m_err; m_message_buf = other.m_message_buf; if (m_err) { @@ -45,7 +45,7 @@ ErrorStorage::ErrorStorage(ErrorStorage&& other) : m_err(std::move(other.m_err)) , m_message_buf(std::move(other.m_message_buf)) { - printf("ErrorStorage::ErrorStorage(ErrorStorage&& other)"); + printf("ErrorStorage::ErrorStorage(ErrorStorage&& other)\n"); if (m_err) { m_err->message = m_message_buf.c_str(); } @@ -54,7 +54,7 @@ ErrorStorage::ErrorStorage(ErrorStorage&& other) ErrorStorage& ErrorStorage::operator=(ErrorStorage&& other) { - printf("ErrorStorage& ErrorStorage::operator=(ErrorStorage&& other)"); + printf("ErrorStorage& ErrorStorage::operator=(ErrorStorage&& other)\n"); m_err = std::move(other.m_err); m_message_buf = std::move(other.m_message_buf); if (m_err) { @@ -77,7 +77,7 @@ bool ErrorStorage::operator==(const ErrorStorage& other) const noexcept void ErrorStorage::assign(std::exception_ptr eptr) noexcept { - printf("void ErrorStorage::assign(std::exception_ptr eptr) noexcept"); + printf("void ErrorStorage::assign(std::exception_ptr eptr) noexcept\n"); if (!eptr) { clear(); return; @@ -152,7 +152,7 @@ bool ErrorStorage::has_error() const noexcept bool ErrorStorage::get_as_realm_error_t(realm_error_t* out) const noexcept { - printf("bool ErrorStorage::get_as_realm_error_t(realm_error_t* out) const noexcept"); + printf("bool ErrorStorage::get_as_realm_error_t(realm_error_t* out) const noexcept\n"); if (!m_err) { return false; } @@ -172,7 +172,7 @@ bool ErrorStorage::clear() noexcept void ErrorStorage::set_usercode_error(void* usercode_error) { - printf("setting usercode_error ptr %p", usercode_error); + printf("setting usercode_error ptr %p\n", usercode_error); m_usercode_error = usercode_error; } @@ -185,7 +185,7 @@ void* ErrorStorage::get_and_clear_usercode_error() ErrorStorage* ErrorStorage::get_thread_local() { - printf("ErrorStorage* ErrorStorage::get_thread_local()"); + printf("ErrorStorage* ErrorStorage::get_thread_local()\n"); #if !defined(RLM_NO_THREAD_LOCAL) static thread_local ErrorStorage g_error_storage; return &g_error_storage; @@ -250,6 +250,6 @@ RLM_EXPORT bool realm_wrap_exceptions(void (*func)()) noexcept RLM_API void realm_register_user_code_callback_error(void* usercode_error) noexcept { - printf("RLM_API void realm_register_user_code_callback_error(void* usercode_error) noexcept"); + printf("RLM_API void realm_register_user_code_callback_error(void* usercode_error) noexcept\n"); realm::c_api::ErrorStorage::get_thread_local()->set_usercode_error(usercode_error); } diff --git a/src/realm/object-store/c_api/types.hpp b/src/realm/object-store/c_api/types.hpp index 92c501d6a3e..c2019a2ba7a 100644 --- a/src/realm/object-store/c_api/types.hpp +++ b/src/realm/object-store/c_api/types.hpp @@ -119,7 +119,7 @@ struct realm_async_error : realm::c_api::WrapC { explicit realm_async_error(std::exception_ptr ep) : error_storage(std::move(ep)) { - printf("explicit realm_async_error(std::exception_ptr ep)"); + printf("explicit realm_async_error(std::exception_ptr ep)\n"); } realm_async_error* clone() const override From 6f227cd96c169d051e5fc03d5e1807dc11a8046e Mon Sep 17 00:00:00 2001 From: blagoev Date: Fri, 11 Aug 2023 21:23:58 +0300 Subject: [PATCH 04/34] Fix ErrorStorage initialization of private fields --- src/realm/object-store/c_api/error.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/realm/object-store/c_api/error.cpp b/src/realm/object-store/c_api/error.cpp index 7fd6f9a3bc6..cd92261f04c 100644 --- a/src/realm/object-store/c_api/error.cpp +++ b/src/realm/object-store/c_api/error.cpp @@ -15,6 +15,8 @@ namespace realm::c_api { ErrorStorage::ErrorStorage(std::exception_ptr ptr) noexcept : m_err(none) , m_message_buf() + , m_usercode_error(nullptr) + { printf("ErrorStorage::ErrorStorage(std::exception_ptr ptr) noexcept\n"); assign(std::move(ptr)); @@ -23,6 +25,7 @@ ErrorStorage::ErrorStorage(std::exception_ptr ptr) noexcept ErrorStorage::ErrorStorage(const ErrorStorage& other) : m_err(other.m_err) , m_message_buf(other.m_message_buf) + , m_usercode_error(other.m_usercode_error) { printf("ErrorStorage::ErrorStorage(const ErrorStorage& other)\n"); if (m_err) { @@ -35,6 +38,7 @@ ErrorStorage& ErrorStorage::operator=(const ErrorStorage& other) printf("ErrorStorage& ErrorStorage::operator=(const ErrorStorage& other)\n"); m_err = other.m_err; m_message_buf = other.m_message_buf; + m_usercode_error = other.m_usercode_error; if (m_err) { m_err->message = m_message_buf.c_str(); } @@ -44,6 +48,7 @@ ErrorStorage& ErrorStorage::operator=(const ErrorStorage& other) ErrorStorage::ErrorStorage(ErrorStorage&& other) : m_err(std::move(other.m_err)) , m_message_buf(std::move(other.m_message_buf)) + , m_usercode_error(std::move(other.m_usercode_error)) { printf("ErrorStorage::ErrorStorage(ErrorStorage&& other)\n"); if (m_err) { @@ -57,6 +62,7 @@ ErrorStorage& ErrorStorage::operator=(ErrorStorage&& other) printf("ErrorStorage& ErrorStorage::operator=(ErrorStorage&& other)\n"); m_err = std::move(other.m_err); m_message_buf = std::move(other.m_message_buf); + m_usercode_error = std::move(other.m_usercode_error); if (m_err) { m_err->message = m_message_buf.c_str(); } @@ -72,7 +78,7 @@ bool ErrorStorage::operator==(const ErrorStorage& other) const noexcept else if (!m_err && !other.m_err) { return true; } - return m_err->error == other.m_err->error && m_message_buf == other.m_message_buf; + return m_err->error == other.m_err->error && m_message_buf == other.m_message_buf; // && m_usercode_error == other.m_usercode_error; //TODO: should we compare the usercode_error here } void ErrorStorage::assign(std::exception_ptr eptr) noexcept From 351eec617db832b3aded2669d4b2d2363b2a3167 Mon Sep 17 00:00:00 2001 From: blagoev Date: Fri, 11 Aug 2023 21:42:14 +0300 Subject: [PATCH 05/34] 23 --- src/realm/object-store/c_api/error.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/realm/object-store/c_api/error.cpp b/src/realm/object-store/c_api/error.cpp index cd92261f04c..39fd214ed45 100644 --- a/src/realm/object-store/c_api/error.cpp +++ b/src/realm/object-store/c_api/error.cpp @@ -160,12 +160,18 @@ bool ErrorStorage::get_as_realm_error_t(realm_error_t* out) const noexcept { printf("bool ErrorStorage::get_as_realm_error_t(realm_error_t* out) const noexcept\n"); if (!m_err) { + printf("no m_err. returning false\n"); return false; } if (out) { + printf("m_err found. returning true\n"); *out = *m_err; + if ((*m_err).usercode_error != nullptr) { + printf("m_err.usercode_error != nullptr\n"); + } } + printf("m_err found. returning true\n"); return true; } From 01d288524bde4b21834c69b07e4281b906c23bde Mon Sep 17 00:00:00 2001 From: blagoev Date: Fri, 11 Aug 2023 22:16:58 +0300 Subject: [PATCH 06/34] 234 --- src/realm/object-store/c_api/error.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/realm/object-store/c_api/error.cpp b/src/realm/object-store/c_api/error.cpp index 39fd214ed45..c87b7fd99cf 100644 --- a/src/realm/object-store/c_api/error.cpp +++ b/src/realm/object-store/c_api/error.cpp @@ -113,8 +113,10 @@ void ErrorStorage::assign(std::exception_ptr eptr) noexcept // Core exceptions: catch (const Exception& ex) { + printf("ErrorStorage error message: %s\n", ex.what()); populate_error(ex, ex.code()); if (ex.code() == ErrorCodes::CallbackFailed) { + printf("ex.code is ErrorCodes::CallbackFailed\n"); m_err->usercode_error = static_cast(ex).usercode_error; } if (ErrorCodes::error_categories(ex.code()).test(ErrorCategory::file_access)) { @@ -126,25 +128,32 @@ void ErrorStorage::assign(std::exception_ptr eptr) noexcept // Generic exceptions: catch (const std::invalid_argument& ex) { + printf("ErrorStorage invalid argument error message: %s\n", ex.what()); populate_error(ex, ErrorCodes::InvalidArgument); } catch (const std::out_of_range& ex) { + printf("ErrorStorage out_of_range error message: %s\n", ex.what()); populate_error(ex, ErrorCodes::OutOfBounds); } catch (const std::logic_error& ex) { + printf("ErrorStorage std::logic_error message: %s\n", ex.what()); populate_error(ex, ErrorCodes::LogicError); } catch (const std::runtime_error& ex) { + printf("ErrorStorage std::runtime_error message: %s\n", ex.what()); populate_error(ex, ErrorCodes::RuntimeError); } catch (const std::bad_alloc& ex) { + printf("ErrorStorage std::exception message: %s\n", ex.what()); populate_error(ex, ErrorCodes::OutOfMemory); } catch (const std::exception& ex) { + printf("ErrorStorage std::exceptio message: %s\n", ex.what()); populate_error(ex, ErrorCodes::UnknownError); } // FIXME: Handle more exception types. catch (...) { + printf("ErrorStorage RLM_ERR_UNKNOWN message\n"); m_err->error = RLM_ERR_UNKNOWN; m_message_buf = "Unknown error"; m_err->message = m_message_buf.c_str(); From fd6a3f9995a0c745c62f03860558017ed8aa3622 Mon Sep 17 00:00:00 2001 From: blagoev Date: Fri, 11 Aug 2023 23:54:40 +0300 Subject: [PATCH 07/34] remove debug stms --- src/realm/object-store/c_api/error.cpp | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/realm/object-store/c_api/error.cpp b/src/realm/object-store/c_api/error.cpp index c87b7fd99cf..184142e7c9b 100644 --- a/src/realm/object-store/c_api/error.cpp +++ b/src/realm/object-store/c_api/error.cpp @@ -18,7 +18,6 @@ ErrorStorage::ErrorStorage(std::exception_ptr ptr) noexcept , m_usercode_error(nullptr) { - printf("ErrorStorage::ErrorStorage(std::exception_ptr ptr) noexcept\n"); assign(std::move(ptr)); } @@ -27,7 +26,6 @@ ErrorStorage::ErrorStorage(const ErrorStorage& other) , m_message_buf(other.m_message_buf) , m_usercode_error(other.m_usercode_error) { - printf("ErrorStorage::ErrorStorage(const ErrorStorage& other)\n"); if (m_err) { m_err->message = m_message_buf.c_str(); } @@ -35,7 +33,6 @@ ErrorStorage::ErrorStorage(const ErrorStorage& other) ErrorStorage& ErrorStorage::operator=(const ErrorStorage& other) { - printf("ErrorStorage& ErrorStorage::operator=(const ErrorStorage& other)\n"); m_err = other.m_err; m_message_buf = other.m_message_buf; m_usercode_error = other.m_usercode_error; @@ -50,7 +47,6 @@ ErrorStorage::ErrorStorage(ErrorStorage&& other) , m_message_buf(std::move(other.m_message_buf)) , m_usercode_error(std::move(other.m_usercode_error)) { - printf("ErrorStorage::ErrorStorage(ErrorStorage&& other)\n"); if (m_err) { m_err->message = m_message_buf.c_str(); } @@ -59,7 +55,6 @@ ErrorStorage::ErrorStorage(ErrorStorage&& other) ErrorStorage& ErrorStorage::operator=(ErrorStorage&& other) { - printf("ErrorStorage& ErrorStorage::operator=(ErrorStorage&& other)\n"); m_err = std::move(other.m_err); m_message_buf = std::move(other.m_message_buf); m_usercode_error = std::move(other.m_usercode_error); @@ -83,7 +78,6 @@ bool ErrorStorage::operator==(const ErrorStorage& other) const noexcept void ErrorStorage::assign(std::exception_ptr eptr) noexcept { - printf("void ErrorStorage::assign(std::exception_ptr eptr) noexcept\n"); if (!eptr) { clear(); return; @@ -113,10 +107,8 @@ void ErrorStorage::assign(std::exception_ptr eptr) noexcept // Core exceptions: catch (const Exception& ex) { - printf("ErrorStorage error message: %s\n", ex.what()); populate_error(ex, ex.code()); if (ex.code() == ErrorCodes::CallbackFailed) { - printf("ex.code is ErrorCodes::CallbackFailed\n"); m_err->usercode_error = static_cast(ex).usercode_error; } if (ErrorCodes::error_categories(ex.code()).test(ErrorCategory::file_access)) { @@ -128,32 +120,25 @@ void ErrorStorage::assign(std::exception_ptr eptr) noexcept // Generic exceptions: catch (const std::invalid_argument& ex) { - printf("ErrorStorage invalid argument error message: %s\n", ex.what()); populate_error(ex, ErrorCodes::InvalidArgument); } catch (const std::out_of_range& ex) { - printf("ErrorStorage out_of_range error message: %s\n", ex.what()); populate_error(ex, ErrorCodes::OutOfBounds); } catch (const std::logic_error& ex) { - printf("ErrorStorage std::logic_error message: %s\n", ex.what()); populate_error(ex, ErrorCodes::LogicError); } catch (const std::runtime_error& ex) { - printf("ErrorStorage std::runtime_error message: %s\n", ex.what()); populate_error(ex, ErrorCodes::RuntimeError); } catch (const std::bad_alloc& ex) { - printf("ErrorStorage std::exception message: %s\n", ex.what()); populate_error(ex, ErrorCodes::OutOfMemory); } catch (const std::exception& ex) { - printf("ErrorStorage std::exceptio message: %s\n", ex.what()); populate_error(ex, ErrorCodes::UnknownError); } // FIXME: Handle more exception types. catch (...) { - printf("ErrorStorage RLM_ERR_UNKNOWN message\n"); m_err->error = RLM_ERR_UNKNOWN; m_message_buf = "Unknown error"; m_err->message = m_message_buf.c_str(); @@ -167,20 +152,14 @@ bool ErrorStorage::has_error() const noexcept bool ErrorStorage::get_as_realm_error_t(realm_error_t* out) const noexcept { - printf("bool ErrorStorage::get_as_realm_error_t(realm_error_t* out) const noexcept\n"); if (!m_err) { - printf("no m_err. returning false\n"); return false; } if (out) { - printf("m_err found. returning true\n"); *out = *m_err; - if ((*m_err).usercode_error != nullptr) { - printf("m_err.usercode_error != nullptr\n"); - } } - printf("m_err found. returning true\n"); + return true; } @@ -193,7 +172,6 @@ bool ErrorStorage::clear() noexcept void ErrorStorage::set_usercode_error(void* usercode_error) { - printf("setting usercode_error ptr %p\n", usercode_error); m_usercode_error = usercode_error; } @@ -206,7 +184,6 @@ void* ErrorStorage::get_and_clear_usercode_error() ErrorStorage* ErrorStorage::get_thread_local() { - printf("ErrorStorage* ErrorStorage::get_thread_local()\n"); #if !defined(RLM_NO_THREAD_LOCAL) static thread_local ErrorStorage g_error_storage; return &g_error_storage; @@ -271,6 +248,5 @@ RLM_EXPORT bool realm_wrap_exceptions(void (*func)()) noexcept RLM_API void realm_register_user_code_callback_error(void* usercode_error) noexcept { - printf("RLM_API void realm_register_user_code_callback_error(void* usercode_error) noexcept\n"); realm::c_api::ErrorStorage::get_thread_local()->set_usercode_error(usercode_error); } From 10357c5fb066dab6152044909d0836e435cabc12 Mon Sep 17 00:00:00 2001 From: blagoev Date: Fri, 11 Aug 2023 23:55:27 +0300 Subject: [PATCH 08/34] remove dbg stms --- src/realm/object-store/c_api/types.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/realm/object-store/c_api/types.hpp b/src/realm/object-store/c_api/types.hpp index c2019a2ba7a..13b8f3ac18d 100644 --- a/src/realm/object-store/c_api/types.hpp +++ b/src/realm/object-store/c_api/types.hpp @@ -113,13 +113,11 @@ struct realm_async_error : realm::c_api::WrapC { explicit realm_async_error(const realm::c_api::ErrorStorage& storage) : error_storage(storage) { - printf("explicit realm_async_error(const realm::c_api::ErrorStorage& storage)\n"); } explicit realm_async_error(std::exception_ptr ep) : error_storage(std::move(ep)) { - printf("explicit realm_async_error(std::exception_ptr ep)\n"); } realm_async_error* clone() const override From d97036f5ea1da6012193e12a1af0182c0b4a8d02 Mon Sep 17 00:00:00 2001 From: blagoev Date: Fri, 11 Aug 2023 23:56:57 +0300 Subject: [PATCH 09/34] remove --- src/realm/object-store/c_api/error.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/realm/object-store/c_api/error.cpp b/src/realm/object-store/c_api/error.cpp index 184142e7c9b..73cc1f08bcb 100644 --- a/src/realm/object-store/c_api/error.cpp +++ b/src/realm/object-store/c_api/error.cpp @@ -159,7 +159,6 @@ bool ErrorStorage::get_as_realm_error_t(realm_error_t* out) const noexcept if (out) { *out = *m_err; } - return true; } From 682942c0c83db5afd87d402e068e5330befa51b3 Mon Sep 17 00:00:00 2001 From: realm-ci Date: Wed, 13 Sep 2023 07:47:19 -0700 Subject: [PATCH 10/34] Updated release notes --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d015379b456..7b7f4288c95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +# NEXT RELEASE + +### Enhancements +* (PR [#????](https://github.com/realm/realm-core/pull/????)) +* None. + +### Fixed +* ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?) +* None. + +### Breaking changes +* None. + +### Compatibility +* Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. + +----------- + +### Internals +* None. + +---------------------------------------------- + # 13.20.1 Release notes ### Enhancements From 673be45ce266cef009ac5af58e8ff7327dd0d234 Mon Sep 17 00:00:00 2001 From: Daniel Tabacaru <96778637+danieltabacaru@users.noreply.github.com> Date: Wed, 13 Sep 2023 21:15:50 +0300 Subject: [PATCH 11/34] Update History Command tool to work with realms with file format version 23 (#6970) * Update History Command tool to work with realms with file format version 23 * changelog * Fix changelog --- CHANGELOG.md | 2 +- src/realm/sync/tools/hist_command.cpp | 50 ++++++++++++--------------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b7f4288c95..3a9fea2644f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ ----------- ### Internals -* None. +* Update History Command tool to work with realms with fileformat v23 ([PR #6970](https://github.com/realm/realm-core/pull/6970)) ---------------------------------------------- diff --git a/src/realm/sync/tools/hist_command.cpp b/src/realm/sync/tools/hist_command.cpp index a11089d717c..31656ede289 100644 --- a/src/realm/sync/tools/hist_command.cpp +++ b/src/realm/sync/tools/hist_command.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -790,20 +791,18 @@ class NullCursorFactory : public RegularCursorFactory { }; -class ClientHistoryCursor_1_to_2 : public RegularSyncHistoryCursor { +class ClientHistoryCursor : public RegularSyncHistoryCursor { public: - ClientHistoryCursor_1_to_2(Allocator& alloc, ref_type root_ref, int schema_version, - version_type current_snapshot_version) + ClientHistoryCursor(Allocator& alloc, ref_type root_ref, int schema_version, + version_type current_snapshot_version) { - REALM_ASSERT(schema_version >= 1 && schema_version <= 2); + REALM_ASSERT(schema_version == 12); if (root_ref == 0) return; // Size of fixed-size arrays std::size_t root_size = 21; - if (schema_version < 2) - root_size = 23; // Slots in root array of history compartment std::size_t changesets_iip = 13; @@ -811,13 +810,6 @@ class ClientHistoryCursor_1_to_2 : public RegularSyncHistoryCursor { std::size_t remote_versions_iip = 15; std::size_t origin_file_idents_iip = 16; std::size_t origin_timestamps_iip = 17; - if (schema_version < 2) { - changesets_iip = 0; - reciprocal_transforms_iip = 1; - remote_versions_iip = 2; - origin_file_idents_iip = 3; - origin_timestamps_iip = 4; - } Array root{alloc}; root.init_from_ref(root_ref); @@ -836,17 +828,17 @@ class ClientHistoryCursor_1_to_2 : public RegularSyncHistoryCursor { { m_remote_versions.reset(new IntegerBpTree(alloc)); // Throws m_remote_versions->set_parent(&root, remote_versions_iip); // Throws - m_remote_versions->create(); + m_remote_versions->init_from_parent(); } { m_origin_file_idents.reset(new IntegerBpTree(alloc)); // Throws m_origin_file_idents->set_parent(&root, origin_file_idents_iip); // Throws - m_origin_file_idents->create(); + m_origin_file_idents->init_from_parent(); } { m_origin_timestamps.reset(new IntegerBpTree(alloc)); // Throws m_origin_timestamps->set_parent(&root, origin_timestamps_iip); // Throws - m_origin_timestamps->create(); + m_origin_timestamps->init_from_parent(); } std::size_t history_size = m_changesets->size(); m_base_version = version_type(current_snapshot_version - history_size); @@ -950,10 +942,10 @@ class ClientHistoryCursor_1_to_2 : public RegularSyncHistoryCursor { }; -class ClientCursorFactory_1_to_2 : public RegularCursorFactory { +class ClientCursorFactory : public RegularCursorFactory { public: - ClientCursorFactory_1_to_2(Allocator& alloc, ref_type root_ref, int schema_version, - version_type current_snapshot_version) noexcept + ClientCursorFactory(Allocator& alloc, ref_type root_ref, int schema_version, + version_type current_snapshot_version) noexcept : m_alloc{alloc} , m_root_ref{root_ref} , m_schema_version{schema_version} @@ -964,8 +956,8 @@ class ClientCursorFactory_1_to_2 : public RegularCursorFactory { protected: std::unique_ptr do_create_history_cursor() override { - return std::make_unique(m_alloc, m_root_ref, m_schema_version, - m_current_snapshot_version); // Throws + return std::make_unique(m_alloc, m_root_ref, m_schema_version, + m_current_snapshot_version); // Throws } std::unique_ptr do_create_client_files_cursor() override @@ -1602,8 +1594,10 @@ void inspect_history(SyncHistoryCursor& cursor, util::Optional buffer.clear(); cursor.get_changeset(buffer); // Throws util::SimpleInputStream in{buffer}; + size_t decompressed_size; + auto decompressed = util::compression::decompress_nonportable_input_stream(in, decompressed_size); sync::Changeset changeset; - sync::parse_changeset(in, changeset); // Throws + sync::parse_changeset(*decompressed, changeset); // Throws expression->reset(changeset); InstructionMatcher matcher{*expression}; bool instr_was_found = false; @@ -1646,8 +1640,10 @@ void inspect_history(SyncHistoryCursor& cursor, util::Optional buffer.clear(); cursor.get_changeset(buffer); // Throws util::SimpleInputStream in{buffer}; + size_t decompressed_size; + auto decompressed = util::compression::decompress_nonportable_input_stream(in, decompressed_size); sync::Changeset changeset; - sync::parse_changeset(in, changeset); // Throws + sync::parse_changeset(*decompressed, changeset); // Throws #if REALM_DEBUG changeset.print(out); // Throws #else @@ -2246,7 +2242,7 @@ int main(int argc, char* argv[]) Group group{realm_path, encryption_key_3}; // Throws using gf = _impl::GroupFriend; int file_format_version = gf::get_file_format_version(group); - if (file_format_version != 9) { + if (file_format_version != 23) { std::cerr << "ERROR: Unexpected file format version " "" << file_format_version << "\n"; // Throws @@ -2267,9 +2263,9 @@ int main(int argc, char* argv[]) int history_schema_version; gf::get_version_and_history_info(alloc, top_ref, version, history_type, history_schema_version); if (history_type == Replication::hist_SyncClient) { - if (history_schema_version >= 1 && history_schema_version <= 2) { - factory = std::make_unique(alloc, history_ref, history_schema_version, - version); // Throws + if (history_schema_version == 12) { + factory = std::make_unique(alloc, history_ref, history_schema_version, + version); // Throws } else { std::cerr << "ERROR: Unsupported schema version " From 2d639a7b3eb5da0dd7c167e1e4bcd9e1d2c5435b Mon Sep 17 00:00:00 2001 From: nicola cabiddu Date: Fri, 15 Sep 2023 19:11:53 +0200 Subject: [PATCH 12/34] Fix open async when rerun on open is set. (#6973) --- CHANGELOG.md | 1 + .../object-store/impl/realm_coordinator.cpp | 24 +- .../object-store/impl/realm_coordinator.hpp | 4 +- .../object-store/sync/async_open_task.cpp | 5 +- test/object-store/sync/flx_sync.cpp | 212 +++++++++++++----- 5 files changed, 178 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a9fea2644f..f27dce29a9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ * When using SecureTransport (i.e. on Apple platforms) only some TLS errors were reported as `TlsHandshakeFailed` and most were reported as `SyncConnectFailed` ([PR #6938](https://github.com/realm/realm-core/pull/6938)). * Sync errors originating from OpenSSL used the error message from the wrong end of the error stack, often resulting in very unhelpful error message ([PR #6938](https://github.com/realm/realm-core/pull/6938)). * Sending empty UPLOAD messages may lead to 'Bad server version' errors and client reset. ([6966](https://github.com/realm/realm-core/issues/6966), since v11.8.0) +* Fix open async in order to invoke the subscription callback correctly when rerun_on_open is set to true. ([#6937](https://github.com/realm/realm-core/pull/6937), since v13.16.0). ### Breaking changes * None. diff --git a/src/realm/object-store/impl/realm_coordinator.cpp b/src/realm/object-store/impl/realm_coordinator.cpp index d6773b12921..f5a8e13c217 100644 --- a/src/realm/object-store/impl/realm_coordinator.cpp +++ b/src/realm/object-store/impl/realm_coordinator.cpp @@ -278,7 +278,7 @@ std::shared_ptr RealmCoordinator::get_realm(Realm::Config config, util::O return realm; } -std::shared_ptr RealmCoordinator::get_realm(std::shared_ptr scheduler) +std::shared_ptr RealmCoordinator::get_realm(std::shared_ptr scheduler, bool first_time_open) { std::shared_ptr realm; util::CheckedUniqueLock lock(m_realm_mutex); @@ -287,7 +287,7 @@ std::shared_ptr RealmCoordinator::get_realm(std::shared_ptr& realm, - util::Optional version, util::CheckedUniqueLock& realm_lock) + util::Optional version, util::CheckedUniqueLock& realm_lock, + bool first_time_open) { - const auto db_opened_first_time = open_db(); + const auto db_created = open_db(); #ifdef REALM_ENABLE_SYNC SyncConfig::SubscriptionInitializerCallback subscription_function = nullptr; bool rerun_on_open = false; if (config.sync_config && config.sync_config->flx_sync_requested && config.sync_config->subscription_initializer) { - subscription_function = std::move(config.sync_config->subscription_initializer); + subscription_function = config.sync_config->subscription_initializer; rerun_on_open = config.sync_config->rerun_init_subscription_on_open; } #else - static_cast(db_opened_first_time); + static_cast(first_time_open); + static_cast(db_created); #endif auto schema = std::move(config.schema); @@ -379,7 +381,15 @@ void RealmCoordinator::do_get_realm(RealmConfig&& config, std::shared_ptr // rerun_on_open was set if (subscription_function) { const auto current_subscription = realm->get_latest_subscription_set(); - if ((current_subscription.version() == 0) || (rerun_on_open && db_opened_first_time)) { + const auto subscription_version = current_subscription.version(); + // in case we are hitting this check while during a normal open, we need to take in + // consideration if the db was created during this call. Since this may be the first time + // we are actually creating a realm. For async open this does not apply, infact db_created + // will always be false. + if (!first_time_open) + first_time_open = db_created; + if (subscription_version == 0 || (first_time_open && rerun_on_open)) { + // if the tasks is cancelled, the subscription may or may not be run. subscription_function(realm); } } diff --git a/src/realm/object-store/impl/realm_coordinator.hpp b/src/realm/object-store/impl/realm_coordinator.hpp index 0c05e1bcbc2..cbfd9b45acf 100644 --- a/src/realm/object-store/impl/realm_coordinator.hpp +++ b/src/realm/object-store/impl/realm_coordinator.hpp @@ -58,7 +58,7 @@ class RealmCoordinator : public std::enable_shared_from_this { // can be read from any thread. std::shared_ptr get_realm(Realm::Config config, util::Optional version) REQUIRES(!m_realm_mutex, !m_schema_cache_mutex); - std::shared_ptr get_realm(std::shared_ptr = nullptr) + std::shared_ptr get_realm(std::shared_ptr = nullptr, bool first_time_open = false) REQUIRES(!m_realm_mutex, !m_schema_cache_mutex); // Return a frozen copy of the source Realm. May return a cached instance @@ -269,7 +269,7 @@ class RealmCoordinator : public std::enable_shared_from_this { std::shared_ptr scheduler = nullptr) REQUIRES(m_realm_mutex); void do_get_realm(Realm::Config&& config, std::shared_ptr& realm, util::Optional version, - util::CheckedUniqueLock& realm_lock) REQUIRES(m_realm_mutex); + util::CheckedUniqueLock& realm_lock, bool first_time_open = false) REQUIRES(m_realm_mutex); void run_async_notifiers() REQUIRES(!m_notifier_mutex, m_running_notifiers_mutex); void clean_up_dead_notifiers() REQUIRES(m_notifier_mutex); diff --git a/src/realm/object-store/sync/async_open_task.cpp b/src/realm/object-store/sync/async_open_task.cpp index e9e136a682f..a15ab3e30b6 100644 --- a/src/realm/object-store/sync/async_open_task.cpp +++ b/src/realm/object-store/sync/async_open_task.cpp @@ -130,10 +130,11 @@ void AsyncOpenTask::attach_to_subscription_initializer(AsyncOpenCallback&& async // 2. we are instructed to run the subscription initializer via sync_config->rerun_init_subscription_on_open. // But we do that only if this is the first time we open realm. - auto shared_realm = coordinator->get_realm(); + auto shared_realm = coordinator->get_realm(nullptr, m_db_first_open); const auto init_subscription = shared_realm->get_latest_subscription_set(); + const auto sub_state = init_subscription.state(); - if (init_subscription.version() == 1 || (rerun_on_launch && m_db_first_open)) { + if ((sub_state != sync::SubscriptionSet::State::Complete) || (m_db_first_open && rerun_on_launch)) { // We need to wait until subscription initializer completes std::shared_ptr self(shared_from_this()); init_subscription.get_state_change_notification(sync::SubscriptionSet::State::Complete) diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index c139db371be..f90abbea240 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -3752,19 +3752,20 @@ TEST_CASE("flx: open realm + register subscription callack while bootstrapping", std::atomic subscription_invoked = false; auto subscription_pf = util::make_promise_future(); // create a subscription to commit when realm is open for the first time or asked to rerun on open - auto init_subscription_callback = [&, promise_holder = util::CopyablePromiseHolder(std::move( - subscription_pf.promise))](std::shared_ptr realm) mutable { - REQUIRE(realm); - auto table = realm->read_group().get_table("class_TopLevel"); - Query query(table); - auto subscription = realm->get_latest_subscription_set(); - auto mutable_subscription = subscription.make_mutable_copy(); - mutable_subscription.insert_or_assign(query); - auto promise = promise_holder.get_promise(); - mutable_subscription.commit(); - subscription_invoked = true; - promise.emplace_value(true); - }; + auto init_subscription_callback_with_promise = + [&, promise_holder = util::CopyablePromiseHolder(std::move(subscription_pf.promise))]( + std::shared_ptr realm) mutable { + REQUIRE(realm); + auto table = realm->read_group().get_table("class_TopLevel"); + Query query(table); + auto subscription = realm->get_latest_subscription_set(); + auto mutable_subscription = subscription.make_mutable_copy(); + mutable_subscription.insert_or_assign(query); + auto promise = promise_holder.get_promise(); + mutable_subscription.commit(); + subscription_invoked = true; + promise.emplace_value(true); + }; // verify that the subscription has changed the database auto verify_subscription = [](SharedRealm realm) { REQUIRE(realm); @@ -3785,7 +3786,7 @@ TEST_CASE("flx: open realm + register subscription callack while bootstrapping", // sync open with subscription callback. Subscription will be run, since this is the first time that realm is // opened subscription_invoked = false; - config.sync_config->subscription_initializer = init_subscription_callback; + config.sync_config->subscription_initializer = init_subscription_callback_with_promise; auto realm = Realm::get_shared_realm(config); REQUIRE(subscription_pf.future.get()); auto sb = realm->get_latest_subscription_set(); @@ -3796,6 +3797,58 @@ TEST_CASE("flx: open realm + register subscription callack while bootstrapping", REQUIRE(verify_subscription(realm)); } + SECTION("Sync Open + Async Open") { + { + subscription_invoked = false; + config.sync_config->subscription_initializer = init_subscription_callback_with_promise; + auto realm = Realm::get_shared_realm(config); + REQUIRE(subscription_pf.future.get()); + auto sb = realm->get_latest_subscription_set(); + auto future = sb.get_state_change_notification(realm::sync::SubscriptionSet::State::Complete); + auto state = future.get(); + REQUIRE(state == realm::sync::SubscriptionSet::State::Complete); + realm->refresh(); // refresh is needed otherwise table_ref->size() would be 0 + REQUIRE(verify_subscription(realm)); + } + { + auto subscription_pf_async = util::make_promise_future(); + auto init_subscription_asyc_callback = + [promise_holder_async = util::CopyablePromiseHolder(std::move(subscription_pf_async.promise))]( + std::shared_ptr realm) mutable { + REQUIRE(realm); + auto table = realm->read_group().get_table("class_TopLevel"); + Query query(table); + auto subscription = realm->get_latest_subscription_set(); + auto mutable_subscription = subscription.make_mutable_copy(); + mutable_subscription.insert_or_assign(query); + auto promise = promise_holder_async.get_promise(); + mutable_subscription.commit(); + promise.emplace_value(true); + }; + auto open_realm_pf = util::make_promise_future(); + auto open_realm_completed_callback = + [&, promise_holder = util::CopyablePromiseHolder(std::move(open_realm_pf.promise))]( + ThreadSafeReference ref, std::exception_ptr err) mutable { + auto promise = promise_holder.get_promise(); + if (err) + promise.emplace_value(false); + else + promise.emplace_value(verify_subscription(Realm::get_shared_realm(std::move(ref)))); + }; + + config.sync_config->subscription_initializer = init_subscription_asyc_callback; + config.sync_config->rerun_init_subscription_on_open = true; + auto async_open = Realm::get_synchronized_realm(config); + async_open->start(open_realm_completed_callback); + REQUIRE(open_realm_pf.future.get()); + REQUIRE(subscription_pf_async.future.get()); + config.sync_config->rerun_init_subscription_on_open = false; + auto realm = Realm::get_shared_realm(config); + REQUIRE(realm->get_latest_subscription_set().version() == 2); + REQUIRE(realm->get_active_subscription_set().version() == 2); + } + } + SECTION("Async open") { SECTION("Initial async open with no rerun on open set") { // subscription will be run since this is the first time we are opening the realm file. @@ -3810,19 +3863,18 @@ TEST_CASE("flx: open realm + register subscription callack while bootstrapping", promise.emplace_value(verify_subscription(Realm::get_shared_realm(std::move(ref)))); }; - subscription_invoked = false; - config.sync_config->subscription_initializer = init_subscription_callback; + config.sync_config->subscription_initializer = init_subscription_callback_with_promise; auto async_open = Realm::get_synchronized_realm(config); async_open->start(open_realm_completed_callback); REQUIRE(open_realm_pf.future.get()); REQUIRE(subscription_pf.future.get()); - SECTION("subscription not run because realm alredy opened no error") { + SECTION("rerun on open = false. Subscription not run") { subscription_invoked = false; auto async_open = Realm::get_synchronized_realm(config); auto open_realm_pf = util::make_promise_future(); auto open_realm_completed_callback = - [&, promise_holder = util::CopyablePromiseHolder(std::move(open_realm_pf.promise))]( + [promise_holder = util::CopyablePromiseHolder(std::move(open_realm_pf.promise))]( ThreadSafeReference, std::exception_ptr) mutable { // no need to verify if the subscription has changed the db, since it has not run as we test // below @@ -3833,13 +3885,25 @@ TEST_CASE("flx: open realm + register subscription callack while bootstrapping", REQUIRE_FALSE(subscription_invoked.load()); } - SECTION("rerun on open set, but this is not the first time the file is opened") { + SECTION("rerun on open = true. Subscription not run cause realm already opened once") { subscription_invoked = false; + auto realm = Realm::get_shared_realm(config); + auto init_subscription = [&subscription_invoked](std::shared_ptr realm) mutable { + REQUIRE(realm); + auto table = realm->read_group().get_table("class_TopLevel"); + Query query(table); + auto subscription = realm->get_latest_subscription_set(); + auto mutable_subscription = subscription.make_mutable_copy(); + mutable_subscription.insert_or_assign(query); + mutable_subscription.commit(); + subscription_invoked.store(true); + }; config.sync_config->rerun_init_subscription_on_open = true; + config.sync_config->subscription_initializer = init_subscription; auto async_open = Realm::get_synchronized_realm(config); auto open_realm_pf = util::make_promise_future(); auto open_realm_completed_callback = - [&, promise_holder = util::CopyablePromiseHolder(std::move(open_realm_pf.promise))]( + [promise_holder = util::CopyablePromiseHolder(std::move(open_realm_pf.promise))]( ThreadSafeReference, std::exception_ptr) mutable { // no need to verify if the subscription has changed the db, since it has not run as we test // below @@ -3848,60 +3912,94 @@ TEST_CASE("flx: open realm + register subscription callack while bootstrapping", async_open->start(open_realm_completed_callback); REQUIRE(open_realm_pf.future.get()); REQUIRE_FALSE(subscription_invoked.load()); + REQUIRE(realm->get_latest_subscription_set().version() == 1); + REQUIRE(realm->get_active_subscription_set().version() == 1); } } SECTION("rerun on open set for multiple async open tasks (subscription runs only once)") { - auto subscription_pf = util::make_promise_future(); - auto init_subscription_callback = - [&, promise_holder = util::CopyablePromiseHolder(std::move(subscription_pf.promise))]( - std::shared_ptr realm) mutable { - REQUIRE(realm); - auto table = realm->read_group().get_table("class_TopLevel"); - Query query(table); - auto subscription = realm->get_latest_subscription_set(); - auto mutable_subscription = subscription.make_mutable_copy(); - mutable_subscription.insert_or_assign(query); - mutable_subscription.commit(); - promise_holder.get_promise().emplace_value(true); - }; - config.sync_config->subscription_initializer = init_subscription_callback; - config.sync_config->rerun_init_subscription_on_open = true; - - auto async_open_task1 = Realm::get_synchronized_realm(config); - auto async_open_task2 = Realm::get_synchronized_realm(config); - - auto open_t1_pf = util::make_promise_future(); - auto open_t2_pf = util::make_promise_future(); + auto init_subscription = [](std::shared_ptr realm) mutable { + REQUIRE(realm); + auto table = realm->read_group().get_table("class_TopLevel"); + Query query(table); + auto subscription = realm->get_latest_subscription_set(); + auto mutable_subscription = subscription.make_mutable_copy(); + mutable_subscription.insert_or_assign(query); + mutable_subscription.commit(); + }; - auto open_callback_task_1 = [&, - promise_holder = util::CopyablePromiseHolder(std::move(open_t1_pf.promise))]( - ThreadSafeReference ref, std::exception_ptr err) mutable { + auto open_task1_pf = util::make_promise_future(); + auto open_task2_pf = util::make_promise_future(); + auto open_callback1 = [promise_holder = util::CopyablePromiseHolder(std::move(open_task1_pf.promise))]( + ThreadSafeReference ref, std::exception_ptr err) mutable { REQUIRE_FALSE(err); auto realm = Realm::get_shared_realm(std::move(ref)); REQUIRE(realm); promise_holder.get_promise().emplace_value(realm); }; - auto open_callback_task_2 = [&, - promise_holder = util::CopyablePromiseHolder(std::move(open_t2_pf.promise))]( - ThreadSafeReference ref, std::exception_ptr err) mutable { + auto open_callback2 = [promise_holder = util::CopyablePromiseHolder(std::move(open_task2_pf.promise))]( + ThreadSafeReference ref, std::exception_ptr err) mutable { REQUIRE_FALSE(err); auto realm = Realm::get_shared_realm(std::move(ref)); REQUIRE(realm); promise_holder.get_promise().emplace_value(realm); }; - async_open_task1->start(open_callback_task_1); - async_open_task2->start(open_callback_task_2); + config.sync_config->rerun_init_subscription_on_open = true; + config.sync_config->subscription_initializer = init_subscription; - // subscription init called only once but realm opened twice - auto realm1 = open_t1_pf.future.get(); - auto realm2 = open_t2_pf.future.get(); - REQUIRE(realm1->get_latest_subscription_set().version() == 1); - REQUIRE(realm1->get_active_subscription_set().version() == 1); - REQUIRE(realm2->get_latest_subscription_set().version() == 1); - REQUIRE(realm2->get_active_subscription_set().version() == 1); - REQUIRE(subscription_pf.future.get()); + SECTION("Realm was already created, but we want to rerun on first open using multiple tasks") { + { + subscription_invoked = false; + auto realm = Realm::get_shared_realm(config); + auto sb = realm->get_latest_subscription_set(); + auto future = sb.get_state_change_notification(realm::sync::SubscriptionSet::State::Complete); + auto state = future.get(); + REQUIRE(state == realm::sync::SubscriptionSet::State::Complete); + realm->refresh(); // refresh is needed otherwise table_ref->size() would be 0 + REQUIRE(verify_subscription(realm)); + REQUIRE(realm->get_latest_subscription_set().version() == 1); + REQUIRE(realm->get_active_subscription_set().version() == 1); + } + + auto async_open_task1 = Realm::get_synchronized_realm(config); + auto async_open_task2 = Realm::get_synchronized_realm(config); + async_open_task1->start(open_callback1); + async_open_task2->start(open_callback2); + + auto realm1 = open_task1_pf.future.get(); + auto realm2 = open_task2_pf.future.get(); + + const auto version_expected = 2; + auto r1_latest = realm1->get_latest_subscription_set().version(); + auto r1_active = realm1->get_active_subscription_set().version(); + REQUIRE(realm2->get_latest_subscription_set().version() == r1_latest); + REQUIRE(realm2->get_active_subscription_set().version() == r1_active); + REQUIRE(r1_latest == version_expected); + REQUIRE(r1_active == version_expected); + } + SECTION("First time realm is created but opened via open async. Both tasks could run the subscription") { + auto async_open_task1 = Realm::get_synchronized_realm(config); + auto async_open_task2 = Realm::get_synchronized_realm(config); + async_open_task1->start(open_callback1); + async_open_task2->start(open_callback2); + auto realm1 = open_task1_pf.future.get(); + auto realm2 = open_task2_pf.future.get(); + + auto r1_latest = realm1->get_latest_subscription_set().version(); + auto r1_active = realm1->get_active_subscription_set().version(); + REQUIRE(realm2->get_latest_subscription_set().version() == r1_latest); + REQUIRE(realm2->get_active_subscription_set().version() == r1_active); + // the callback may be run twice, if task1 is the first task to open realm + // but it is scheduled after tasks2, which have opened realm later but + // by the time it runs, subscription version is equal to 0 (realm creation). + // This can only happen the first time that realm is created. All the other times + // the init_sb callback is guaranteed to run once. + REQUIRE(r1_latest >= 1); + REQUIRE(r1_latest <= 2); + REQUIRE(r1_active >= 1); + REQUIRE(r1_active <= 2); + } } } } From 4b413aa827daa7ee67b975342cde01556c90b2a6 Mon Sep 17 00:00:00 2001 From: James Stone Date: Fri, 15 Sep 2023 11:36:51 -0700 Subject: [PATCH 13/34] Use an enum class instead of int constants (#6968) * prefer a strongly typed enum class to int constants * fix MSVC warning * fix a warning --- src/realm/parser/driver.cpp | 168 ++++++++++++++------- src/realm/parser/driver.hpp | 75 ++++----- src/realm/parser/generated/query_bison.cpp | 111 ++++++++------ src/realm/parser/generated/query_bison.hpp | 108 +++++++------ src/realm/parser/query_bison.yy | 32 ++-- 5 files changed, 297 insertions(+), 197 deletions(-) diff --git a/src/realm/parser/driver.cpp b/src/realm/parser/driver.cpp index 376ca96f68b..51ff4c550cc 100644 --- a/src/realm/parser/driver.cpp +++ b/src/realm/parser/driver.cpp @@ -72,21 +72,6 @@ const char* expression_cmp_type_to_str(util::Optional return ""; } - -static std::map opstr = { - {CompareNode::EQUAL, "="}, - {CompareNode::NOT_EQUAL, "!="}, - {CompareNode::GREATER, ">"}, - {CompareNode::LESS, "<"}, - {CompareNode::GREATER_EQUAL, ">="}, - {CompareNode::LESS_EQUAL, "<="}, - {CompareNode::BEGINSWITH, "beginswith"}, - {CompareNode::ENDSWITH, "endswith"}, - {CompareNode::CONTAINS, "contains"}, - {CompareNode::LIKE, "like"}, - {CompareNode::IN, "in"}, -}; - std::string print_pretty_objlink(const ObjLink& link, const Group* g) { REALM_ASSERT(g); @@ -311,6 +296,37 @@ namespace realm { namespace query_parser { +std::string_view string_for_op(CompareType op) +{ + switch (op) { + case CompareType::EQUAL: + return "="; + case CompareType::NOT_EQUAL: + return "!="; + case CompareType::GREATER: + return ">"; + case CompareType::LESS: + return "<"; + case CompareType::GREATER_EQUAL: + return ">="; + case CompareType::LESS_EQUAL: + return "<="; + case CompareType::BEGINSWITH: + return "beginswith"; + case CompareType::ENDSWITH: + return "endswith"; + case CompareType::CONTAINS: + return "contains"; + case CompareType::LIKE: + return "like"; + case CompareType::IN: + return "in"; + case CompareType::TEXT: + return "text"; + } + return ""; // appease MSVC warnings +} + NoArguments ParserDriver::s_default_args; query_parser::KeyPathMapping ParserDriver::s_default_mapping; @@ -348,7 +364,7 @@ Query AndNode::visit(ParserDriver* drv) return q; } -static void verify_only_string_types(DataType type, const std::string& op_string) +static void verify_only_string_types(DataType type, std::string_view op_string) { if (type != type_String && type != type_Binary && type != type_Mixed) { throw InvalidQueryError(util::format( @@ -502,7 +518,7 @@ Query EqualityNode::visit(ParserDriver* drv) } } - if (op == CompareNode::IN) { + if (op == CompareType::IN) { Subexpr* r = right.get(); if (!r->has_multiple_values()) { throw InvalidQueryArgError("The keypath following 'IN' must contain a list. Found '" + @@ -523,11 +539,11 @@ Query EqualityNode::visit(ParserDriver* drv) for (auto val : *link_values) { values.emplace_back(val.is_null() ? ObjKey() : val.get()); } - if (op == CompareNode::EQUAL) { + if (op == CompareType::EQUAL) { return drv->m_base_table->where().links_to(link_column->link_map().get_first_column_key(), values); } - else if (op == CompareNode::NOT_EQUAL) { + else if (op == CompareType::NOT_EQUAL) { return drv->m_base_table->where().not_links_to(link_column->link_map().get_first_column_key(), values); } @@ -541,11 +557,13 @@ Query EqualityNode::visit(ParserDriver* drv) auto col_key = prop->column_key(); if (val.is_null()) { switch (op) { - case CompareNode::EQUAL: - case CompareNode::IN: + case CompareType::EQUAL: + case CompareType::IN: return drv->m_base_table->where().equal(col_key, realm::null()); - case CompareNode::NOT_EQUAL: + case CompareType::NOT_EQUAL: return drv->m_base_table->where().not_equal(col_key, realm::null()); + default: + break; } } switch (left->get_type()) { @@ -578,22 +596,26 @@ Query EqualityNode::visit(ParserDriver* drv) } if (case_sensitive) { switch (op) { - case CompareNode::EQUAL: - case CompareNode::IN: + case CompareType::EQUAL: + case CompareType::IN: return Query(std::unique_ptr(new Compare(std::move(left), std::move(right)))); - case CompareNode::NOT_EQUAL: + case CompareType::NOT_EQUAL: return Query(std::unique_ptr(new Compare(std::move(left), std::move(right)))); + default: + break; } } else { - verify_only_string_types(right_type, opstr[op] + "[c]"); + verify_only_string_types(right_type, util::format("%1%2", string_for_op(op), "[c]")); switch (op) { - case CompareNode::EQUAL: - case CompareNode::IN: + case CompareType::EQUAL: + case CompareType::IN: return Query(std::unique_ptr(new Compare(std::move(left), std::move(right)))); - case CompareNode::NOT_EQUAL: + case CompareType::NOT_EQUAL: return Query( std::unique_ptr(new Compare(std::move(left), std::move(right)))); + default: + break; } } return {}; @@ -615,8 +637,8 @@ Query BetweenNode::visit(ParserDriver* drv) auto& min(limits->elements.at(0)); auto& max(limits->elements.at(1)); - RelationalNode cmp1(prop, CompareNode::GREATER_EQUAL, min); - RelationalNode cmp2(prop, CompareNode::LESS_EQUAL, max); + RelationalNode cmp1(prop, CompareType::GREATER_EQUAL, min); + RelationalNode cmp2(prop, CompareType::LESS_EQUAL, max); Query q(drv->m_base_table); q.and_query(cmp1.visit(drv)); @@ -638,7 +660,7 @@ Query RelationalNode::visit(ParserDriver* drv) if (left_type == type_Link || left_type == type_TypeOfValue) { throw InvalidQueryError(util::format( "Unsupported operator %1 in query. Only equal (==) and not equal (!=) are supported for this type.", - opstr[op])); + string_for_op(op))); } if (!(left_type_is_null || right_type_is_null) && (!left_type.is_valid() || !right_type.is_valid() || @@ -685,14 +707,16 @@ Query RelationalNode::visit(ParserDriver* drv) } } switch (op) { - case CompareNode::GREATER: + case CompareType::GREATER: return Query(std::unique_ptr(new Compare(std::move(left), std::move(right)))); - case CompareNode::LESS: + case CompareType::LESS: return Query(std::unique_ptr(new Compare(std::move(left), std::move(right)))); - case CompareNode::GREATER_EQUAL: + case CompareType::GREATER_EQUAL: return Query(std::unique_ptr(new Compare(std::move(left), std::move(right)))); - case CompareNode::LESS_EQUAL: + case CompareType::LESS_EQUAL: return Query(std::unique_ptr(new Compare(std::move(left), std::move(right)))); + default: + break; } return {}; } @@ -705,7 +729,7 @@ Query StringOpsNode::visit(ParserDriver* drv) auto right_type = right->get_type(); const ObjPropertyBase* prop = dynamic_cast(left.get()); - verify_only_string_types(right_type, opstr[op]); + verify_only_string_types(right_type, string_for_op(op)); if (prop && !prop->links_exist() && right->has_single_value() && (left_type == right_type || left_type == type_Mixed)) { @@ -714,64 +738,98 @@ Query StringOpsNode::visit(ParserDriver* drv) StringData val = right->get_mixed().get_string(); switch (op) { - case CompareNode::BEGINSWITH: + case CompareType::BEGINSWITH: return drv->m_base_table->where().begins_with(col_key, val, case_sensitive); - case CompareNode::ENDSWITH: + case CompareType::ENDSWITH: return drv->m_base_table->where().ends_with(col_key, val, case_sensitive); - case CompareNode::CONTAINS: + case CompareType::CONTAINS: return drv->m_base_table->where().contains(col_key, val, case_sensitive); - case CompareNode::LIKE: + case CompareType::LIKE: return drv->m_base_table->where().like(col_key, val, case_sensitive); - case CompareNode::TEXT: + case CompareType::TEXT: return drv->m_base_table->where().fulltext(col_key, val); + case CompareType::IN: + case CompareType::EQUAL: + case CompareType::NOT_EQUAL: + case CompareType::GREATER: + case CompareType::LESS: + case CompareType::GREATER_EQUAL: + case CompareType::LESS_EQUAL: + break; } } else if (right_type == type_Binary) { BinaryData val = right->get_mixed().get_binary(); switch (op) { - case CompareNode::BEGINSWITH: + case CompareType::BEGINSWITH: return drv->m_base_table->where().begins_with(col_key, val, case_sensitive); - case CompareNode::ENDSWITH: + case CompareType::ENDSWITH: return drv->m_base_table->where().ends_with(col_key, val, case_sensitive); - case CompareNode::CONTAINS: + case CompareType::CONTAINS: return drv->m_base_table->where().contains(col_key, val, case_sensitive); - case CompareNode::LIKE: + case CompareType::LIKE: return drv->m_base_table->where().like(col_key, val, case_sensitive); + case CompareType::TEXT: + case CompareType::IN: + case CompareType::EQUAL: + case CompareType::NOT_EQUAL: + case CompareType::GREATER: + case CompareType::LESS: + case CompareType::GREATER_EQUAL: + case CompareType::LESS_EQUAL: + break; } } } if (case_sensitive) { switch (op) { - case CompareNode::BEGINSWITH: + case CompareType::BEGINSWITH: return Query(std::unique_ptr(new Compare(std::move(right), std::move(left)))); - case CompareNode::ENDSWITH: + case CompareType::ENDSWITH: return Query(std::unique_ptr(new Compare(std::move(right), std::move(left)))); - case CompareNode::CONTAINS: + case CompareType::CONTAINS: return Query(std::unique_ptr(new Compare(std::move(right), std::move(left)))); - case CompareNode::LIKE: + case CompareType::LIKE: return Query(std::unique_ptr(new Compare(std::move(right), std::move(left)))); - case CompareNode::TEXT: { + case CompareType::TEXT: { StringData val = right->get_mixed().get_string(); auto string_prop = dynamic_cast*>(left.get()); return string_prop->fulltext(val); } + case CompareType::IN: + case CompareType::EQUAL: + case CompareType::NOT_EQUAL: + case CompareType::GREATER: + case CompareType::LESS: + case CompareType::GREATER_EQUAL: + case CompareType::LESS_EQUAL: + break; } } else { switch (op) { - case CompareNode::BEGINSWITH: + case CompareType::BEGINSWITH: return Query( std::unique_ptr(new Compare(std::move(right), std::move(left)))); - case CompareNode::ENDSWITH: + case CompareType::ENDSWITH: return Query( std::unique_ptr(new Compare(std::move(right), std::move(left)))); - case CompareNode::CONTAINS: + case CompareType::CONTAINS: return Query( std::unique_ptr(new Compare(std::move(right), std::move(left)))); - case CompareNode::LIKE: + case CompareType::LIKE: return Query(std::unique_ptr(new Compare(std::move(right), std::move(left)))); + case CompareType::IN: + case CompareType::EQUAL: + case CompareType::NOT_EQUAL: + case CompareType::GREATER: + case CompareType::LESS: + case CompareType::GREATER_EQUAL: + case CompareType::LESS_EQUAL: + case CompareType::TEXT: + break; } } return {}; diff --git a/src/realm/parser/driver.hpp b/src/realm/parser/driver.hpp index e1271aac6fd..83dd6a297ab 100644 --- a/src/realm/parser/driver.hpp +++ b/src/realm/parser/driver.hpp @@ -410,29 +410,32 @@ class OperationNode : public ExpressionNode { /******************************* Compare Nodes *******************************/ -class CompareNode : public QueryNode { -public: - static constexpr int EQUAL = 0; - static constexpr int NOT_EQUAL = 1; - static constexpr int GREATER = 2; - static constexpr int LESS = 3; - static constexpr int GREATER_EQUAL = 4; - static constexpr int LESS_EQUAL = 5; - static constexpr int BEGINSWITH = 6; - static constexpr int ENDSWITH = 7; - static constexpr int CONTAINS = 8; - static constexpr int LIKE = 9; - static constexpr int IN = 10; - static constexpr int TEXT = 11; +enum class CompareType : char { + EQUAL, + NOT_EQUAL, + GREATER, + LESS, + GREATER_EQUAL, + LESS_EQUAL, + BEGINSWITH, + ENDSWITH, + CONTAINS, + LIKE, + IN, + TEXT, }; +std::string_view string_for_op(CompareType op); + +class CompareNode : public QueryNode {}; + class EqualityNode : public CompareNode { public: std::vector values; - int op; + CompareType op; bool case_sensitive = true; - EqualityNode(ExpressionNode* left, int t, ExpressionNode* right) + EqualityNode(ExpressionNode* left, CompareType t, ExpressionNode* right) : op(t) { values.emplace_back(left); @@ -444,9 +447,9 @@ class EqualityNode : public CompareNode { class RelationalNode : public CompareNode { public: std::vector values; - int op; + CompareType op; - RelationalNode(ExpressionNode* left, int t, ExpressionNode* right) + RelationalNode(ExpressionNode* left, CompareType t, ExpressionNode* right) : op(t) { values.emplace_back(left); @@ -471,10 +474,10 @@ class BetweenNode : public CompareNode { class StringOpsNode : public CompareNode { public: std::vector values; - int op; + CompareType op; bool case_sensitive = true; - StringOpsNode(ValueNode* left, int t, ValueNode* right) + StringOpsNode(ValueNode* left, CompareType t, ValueNode* right) : op(t) { values.emplace_back(left); @@ -628,9 +631,9 @@ class ParserDriver { double get_arg_for_coordinate(const std::string&); template - Query simple_query(int op, ColKey col_key, T val, bool case_sensitive); + Query simple_query(CompareType op, ColKey col_key, T val, bool case_sensitive); template - Query simple_query(int op, ColKey col_key, T val); + Query simple_query(CompareType op, ColKey col_key, T val); std::pair cmp(const std::vector& values); SubexprPtr column(LinkChain&, const std::string&); SubexprPtr dictionary_column(LinkChain&, const std::string&); @@ -649,35 +652,39 @@ class ParserDriver { }; template -Query ParserDriver::simple_query(int op, ColKey col_key, T val, bool case_sensitive) +Query ParserDriver::simple_query(CompareType op, ColKey col_key, T val, bool case_sensitive) { switch (op) { - case CompareNode::IN: - case CompareNode::EQUAL: + case CompareType::IN: + case CompareType::EQUAL: return m_base_table->where().equal(col_key, val, case_sensitive); - case CompareNode::NOT_EQUAL: + case CompareType::NOT_EQUAL: return m_base_table->where().not_equal(col_key, val, case_sensitive); + default: + break; } return m_base_table->where(); } template -Query ParserDriver::simple_query(int op, ColKey col_key, T val) +Query ParserDriver::simple_query(CompareType op, ColKey col_key, T val) { switch (op) { - case CompareNode::IN: - case CompareNode::EQUAL: + case CompareType::IN: + case CompareType::EQUAL: return m_base_table->where().equal(col_key, val); - case CompareNode::NOT_EQUAL: + case CompareType::NOT_EQUAL: return m_base_table->where().not_equal(col_key, val); - case CompareNode::GREATER: + case CompareType::GREATER: return m_base_table->where().greater(col_key, val); - case CompareNode::LESS: + case CompareType::LESS: return m_base_table->where().less(col_key, val); - case CompareNode::GREATER_EQUAL: + case CompareType::GREATER_EQUAL: return m_base_table->where().greater_equal(col_key, val); - case CompareNode::LESS_EQUAL: + case CompareType::LESS_EQUAL: return m_base_table->where().less_equal(col_key, val); + default: + break; } return m_base_table->where(); } diff --git a/src/realm/parser/generated/query_bison.cpp b/src/realm/parser/generated/query_bison.cpp index 68d82c85e36..8764fada001 100644 --- a/src/realm/parser/generated/query_bison.cpp +++ b/src/realm/parser/generated/query_bison.cpp @@ -198,6 +198,12 @@ namespace yy { value.YY_MOVE_OR_COPY< AggrNode* > (YY_MOVE (that.value)); break; + case symbol_kind::SYM_equality: // equality + case symbol_kind::SYM_relational: // relational + case symbol_kind::SYM_stringop: // stringop + value.YY_MOVE_OR_COPY< CompareType > (YY_MOVE (that.value)); + break; + case symbol_kind::SYM_constant: // constant case symbol_kind::SYM_primary_key: // primary_key value.YY_MOVE_OR_COPY< ConstantNode* > (YY_MOVE (that.value)); @@ -275,9 +281,6 @@ namespace yy { case symbol_kind::SYM_comp_type: // comp_type case symbol_kind::SYM_aggr_op: // aggr_op - case symbol_kind::SYM_equality: // equality - case symbol_kind::SYM_relational: // relational - case symbol_kind::SYM_stringop: // stringop value.YY_MOVE_OR_COPY< int > (YY_MOVE (that.value)); break; @@ -339,6 +342,12 @@ namespace yy { value.move< AggrNode* > (YY_MOVE (that.value)); break; + case symbol_kind::SYM_equality: // equality + case symbol_kind::SYM_relational: // relational + case symbol_kind::SYM_stringop: // stringop + value.move< CompareType > (YY_MOVE (that.value)); + break; + case symbol_kind::SYM_constant: // constant case symbol_kind::SYM_primary_key: // primary_key value.move< ConstantNode* > (YY_MOVE (that.value)); @@ -416,9 +425,6 @@ namespace yy { case symbol_kind::SYM_comp_type: // comp_type case symbol_kind::SYM_aggr_op: // aggr_op - case symbol_kind::SYM_equality: // equality - case symbol_kind::SYM_relational: // relational - case symbol_kind::SYM_stringop: // stringop value.move< int > (YY_MOVE (that.value)); break; @@ -480,6 +486,12 @@ namespace yy { value.copy< AggrNode* > (that.value); break; + case symbol_kind::SYM_equality: // equality + case symbol_kind::SYM_relational: // relational + case symbol_kind::SYM_stringop: // stringop + value.copy< CompareType > (that.value); + break; + case symbol_kind::SYM_constant: // constant case symbol_kind::SYM_primary_key: // primary_key value.copy< ConstantNode* > (that.value); @@ -557,9 +569,6 @@ namespace yy { case symbol_kind::SYM_comp_type: // comp_type case symbol_kind::SYM_aggr_op: // aggr_op - case symbol_kind::SYM_equality: // equality - case symbol_kind::SYM_relational: // relational - case symbol_kind::SYM_stringop: // stringop value.copy< int > (that.value); break; @@ -619,6 +628,12 @@ namespace yy { value.move< AggrNode* > (that.value); break; + case symbol_kind::SYM_equality: // equality + case symbol_kind::SYM_relational: // relational + case symbol_kind::SYM_stringop: // stringop + value.move< CompareType > (that.value); + break; + case symbol_kind::SYM_constant: // constant case symbol_kind::SYM_primary_key: // primary_key value.move< ConstantNode* > (that.value); @@ -696,9 +711,6 @@ namespace yy { case symbol_kind::SYM_comp_type: // comp_type case symbol_kind::SYM_aggr_op: // aggr_op - case symbol_kind::SYM_equality: // equality - case symbol_kind::SYM_relational: // relational - case symbol_kind::SYM_stringop: // stringop value.move< int > (that.value); break; @@ -1187,15 +1199,15 @@ namespace yy { break; case symbol_kind::SYM_equality: // equality - { yyo << yysym.value.template as < int > (); } + { yyo << string_for_op(yysym.value.template as < CompareType > ()); } break; case symbol_kind::SYM_relational: // relational - { yyo << yysym.value.template as < int > (); } + { yyo << string_for_op(yysym.value.template as < CompareType > ()); } break; case symbol_kind::SYM_stringop: // stringop - { yyo << yysym.value.template as < int > (); } + { yyo << string_for_op(yysym.value.template as < CompareType > ()); } break; case symbol_kind::SYM_path: // path @@ -1438,6 +1450,12 @@ namespace yy { yylhs.value.emplace< AggrNode* > (); break; + case symbol_kind::SYM_equality: // equality + case symbol_kind::SYM_relational: // relational + case symbol_kind::SYM_stringop: // stringop + yylhs.value.emplace< CompareType > (); + break; + case symbol_kind::SYM_constant: // constant case symbol_kind::SYM_primary_key: // primary_key yylhs.value.emplace< ConstantNode* > (); @@ -1515,9 +1533,6 @@ namespace yy { case symbol_kind::SYM_comp_type: // comp_type case symbol_kind::SYM_aggr_op: // aggr_op - case symbol_kind::SYM_equality: // equality - case symbol_kind::SYM_relational: // relational - case symbol_kind::SYM_stringop: // stringop yylhs.value.emplace< int > (); break; @@ -1603,32 +1618,32 @@ namespace yy { break; case 9: // compare: expr equality expr - { yylhs.value.as < QueryNode* > () = drv.m_parse_nodes.create(yystack_[2].value.as < ExpressionNode* > (), yystack_[1].value.as < int > (), yystack_[0].value.as < ExpressionNode* > ()); } + { yylhs.value.as < QueryNode* > () = drv.m_parse_nodes.create(yystack_[2].value.as < ExpressionNode* > (), yystack_[1].value.as < CompareType > (), yystack_[0].value.as < ExpressionNode* > ()); } break; case 10: // compare: expr equality "[c]" expr { - auto tmp = drv.m_parse_nodes.create(yystack_[3].value.as < ExpressionNode* > (), yystack_[2].value.as < int > (), yystack_[0].value.as < ExpressionNode* > ()); + auto tmp = drv.m_parse_nodes.create(yystack_[3].value.as < ExpressionNode* > (), yystack_[2].value.as < CompareType > (), yystack_[0].value.as < ExpressionNode* > ()); tmp->case_sensitive = false; yylhs.value.as < QueryNode* > () = tmp; } break; case 11: // compare: expr relational expr - { yylhs.value.as < QueryNode* > () = drv.m_parse_nodes.create(yystack_[2].value.as < ExpressionNode* > (), yystack_[1].value.as < int > (), yystack_[0].value.as < ExpressionNode* > ()); } + { yylhs.value.as < QueryNode* > () = drv.m_parse_nodes.create(yystack_[2].value.as < ExpressionNode* > (), yystack_[1].value.as < CompareType > (), yystack_[0].value.as < ExpressionNode* > ()); } break; case 12: // compare: value stringop value - { yylhs.value.as < QueryNode* > () = drv.m_parse_nodes.create(yystack_[2].value.as < ValueNode* > (), yystack_[1].value.as < int > (), yystack_[0].value.as < ValueNode* > ()); } + { yylhs.value.as < QueryNode* > () = drv.m_parse_nodes.create(yystack_[2].value.as < ValueNode* > (), yystack_[1].value.as < CompareType > (), yystack_[0].value.as < ValueNode* > ()); } break; case 13: // compare: value "fulltext" value - { yylhs.value.as < QueryNode* > () = drv.m_parse_nodes.create(yystack_[2].value.as < ValueNode* > (), CompareNode::TEXT, yystack_[0].value.as < ValueNode* > ()); } + { yylhs.value.as < QueryNode* > () = drv.m_parse_nodes.create(yystack_[2].value.as < ValueNode* > (), CompareType::TEXT, yystack_[0].value.as < ValueNode* > ()); } break; case 14: // compare: value stringop "[c]" value { - auto tmp = drv.m_parse_nodes.create(yystack_[3].value.as < ValueNode* > (), yystack_[2].value.as < int > (), yystack_[0].value.as < ValueNode* > ()); + auto tmp = drv.m_parse_nodes.create(yystack_[3].value.as < ValueNode* > (), yystack_[2].value.as < CompareType > (), yystack_[0].value.as < ValueNode* > ()); tmp->case_sensitive = false; yylhs.value.as < QueryNode* > () = tmp; } @@ -1973,47 +1988,47 @@ namespace yy { break; case 97: // equality: "==" - { yylhs.value.as < int > () = CompareNode::EQUAL; } + { yylhs.value.as < CompareType > () = CompareType::EQUAL; } break; case 98: // equality: "!=" - { yylhs.value.as < int > () = CompareNode::NOT_EQUAL; } + { yylhs.value.as < CompareType > () = CompareType::NOT_EQUAL; } break; case 99: // equality: "in" - { yylhs.value.as < int > () = CompareNode::IN; } + { yylhs.value.as < CompareType > () = CompareType::IN; } break; case 100: // relational: "<" - { yylhs.value.as < int > () = CompareNode::LESS; } + { yylhs.value.as < CompareType > () = CompareType::LESS; } break; case 101: // relational: "<=" - { yylhs.value.as < int > () = CompareNode::LESS_EQUAL; } + { yylhs.value.as < CompareType > () = CompareType::LESS_EQUAL; } break; case 102: // relational: ">" - { yylhs.value.as < int > () = CompareNode::GREATER; } + { yylhs.value.as < CompareType > () = CompareType::GREATER; } break; case 103: // relational: ">=" - { yylhs.value.as < int > () = CompareNode::GREATER_EQUAL; } + { yylhs.value.as < CompareType > () = CompareType::GREATER_EQUAL; } break; case 104: // stringop: "beginswith" - { yylhs.value.as < int > () = CompareNode::BEGINSWITH; } + { yylhs.value.as < CompareType > () = CompareType::BEGINSWITH; } break; case 105: // stringop: "endswith" - { yylhs.value.as < int > () = CompareNode::ENDSWITH; } + { yylhs.value.as < CompareType > () = CompareType::ENDSWITH; } break; case 106: // stringop: "contains" - { yylhs.value.as < int > () = CompareNode::CONTAINS; } + { yylhs.value.as < CompareType > () = CompareType::CONTAINS; } break; case 107: // stringop: "like" - { yylhs.value.as < int > () = CompareNode::LIKE; } + { yylhs.value.as < CompareType > () = CompareType::LIKE; } break; case 108: // path: path_elem @@ -2745,19 +2760,19 @@ namespace yy { const short parser::yyrline_[] = { - 0, 187, 187, 190, 191, 192, 193, 194, 195, 198, - 199, 204, 205, 206, 207, 212, 213, 214, 217, 218, - 219, 220, 221, 222, 225, 226, 227, 228, 229, 232, - 233, 236, 240, 246, 249, 252, 253, 254, 257, 258, - 261, 262, 264, 267, 268, 271, 272, 273, 276, 277, - 278, 279, 281, 284, 285, 287, 290, 291, 293, 296, - 297, 299, 300, 303, 304, 305, 308, 309, 310, 311, - 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, - 329, 330, 331, 332, 333, 336, 337, 340, 341, 342, - 345, 346, 347, 350, 351, 352, 353, 356, 357, 358, - 361, 362, 363, 364, 367, 368, 369, 370, 373, 374, - 377, 378, 379, 380, 383, 384, 385, 386, 387, 388, - 389, 390, 391, 392, 393, 394, 395, 396, 397 + 0, 191, 191, 194, 195, 196, 197, 198, 199, 202, + 203, 208, 209, 210, 211, 216, 217, 218, 221, 222, + 223, 224, 225, 226, 229, 230, 231, 232, 233, 236, + 237, 240, 244, 250, 253, 256, 257, 258, 261, 262, + 265, 266, 268, 271, 272, 275, 276, 277, 280, 281, + 282, 283, 285, 288, 289, 291, 294, 295, 297, 300, + 301, 303, 304, 307, 308, 309, 312, 313, 314, 315, + 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, + 333, 334, 335, 336, 337, 340, 341, 344, 345, 346, + 349, 350, 351, 354, 355, 356, 357, 360, 361, 362, + 365, 366, 367, 368, 371, 372, 373, 374, 377, 378, + 381, 382, 383, 384, 387, 388, 389, 390, 391, 392, + 393, 394, 395, 396, 397, 398, 399, 400, 401 }; void diff --git a/src/realm/parser/generated/query_bison.hpp b/src/realm/parser/generated/query_bison.hpp index 84fe32a2c23..8c5e39dbe46 100644 --- a/src/realm/parser/generated/query_bison.hpp +++ b/src/realm/parser/generated/query_bison.hpp @@ -72,6 +72,8 @@ class DescriptorNode; class PropertyNode; class SubqueryNode; + + enum class CompareType: char; struct PathElem { std::string id; Mixed index; @@ -447,74 +449,76 @@ namespace yy { // aggregate char dummy1[sizeof (AggrNode*)]; + // equality + // relational + // stringop + char dummy2[sizeof (CompareType)]; + // constant // primary_key - char dummy2[sizeof (ConstantNode*)]; + char dummy3[sizeof (ConstantNode*)]; // distinct // distinct_param // sort // sort_param // limit - char dummy3[sizeof (DescriptorNode*)]; + char dummy4[sizeof (DescriptorNode*)]; // post_query - char dummy4[sizeof (DescriptorOrderingNode*)]; + char dummy5[sizeof (DescriptorOrderingNode*)]; // expr - char dummy5[sizeof (ExpressionNode*)]; + char dummy6[sizeof (ExpressionNode*)]; // geoloop_content // geoloop // geopoly_content // geospatial - char dummy6[sizeof (GeospatialNode*)]; + char dummy7[sizeof (GeospatialNode*)]; // list // list_content - char dummy7[sizeof (ListNode*)]; + char dummy8[sizeof (ListNode*)]; // path_elem - char dummy8[sizeof (PathElem)]; + char dummy9[sizeof (PathElem)]; // path - char dummy9[sizeof (PathNode*)]; + char dummy10[sizeof (PathNode*)]; // post_op - char dummy10[sizeof (PostOpNode*)]; + char dummy11[sizeof (PostOpNode*)]; // prop // simple_prop - char dummy11[sizeof (PropertyNode*)]; + char dummy12[sizeof (PropertyNode*)]; // query // compare - char dummy12[sizeof (QueryNode*)]; + char dummy13[sizeof (QueryNode*)]; // subquery - char dummy13[sizeof (SubqueryNode*)]; + char dummy14[sizeof (SubqueryNode*)]; // boolexpr - char dummy14[sizeof (TrueOrFalseNode*)]; + char dummy15[sizeof (TrueOrFalseNode*)]; // value - char dummy15[sizeof (ValueNode*)]; + char dummy16[sizeof (ValueNode*)]; // direction - char dummy16[sizeof (bool)]; + char dummy17[sizeof (bool)]; // coordinate - char dummy17[sizeof (double)]; + char dummy18[sizeof (double)]; // comp_type // aggr_op - // equality - // relational - // stringop - char dummy18[sizeof (int)]; + char dummy19[sizeof (int)]; // geopoint - char dummy19[sizeof (std::optional)]; + char dummy20[sizeof (std::optional)]; // "identifier" // "string" @@ -548,7 +552,7 @@ namespace yy { // "@type" // "key or value" // id - char dummy20[sizeof (std::string)]; + char dummy21[sizeof (std::string)]; }; /// The size of the largest semantic type. @@ -819,6 +823,12 @@ namespace yy { value.move< AggrNode* > (std::move (that.value)); break; + case symbol_kind::SYM_equality: // equality + case symbol_kind::SYM_relational: // relational + case symbol_kind::SYM_stringop: // stringop + value.move< CompareType > (std::move (that.value)); + break; + case symbol_kind::SYM_constant: // constant case symbol_kind::SYM_primary_key: // primary_key value.move< ConstantNode* > (std::move (that.value)); @@ -896,9 +906,6 @@ namespace yy { case symbol_kind::SYM_comp_type: // comp_type case symbol_kind::SYM_aggr_op: // aggr_op - case symbol_kind::SYM_equality: // equality - case symbol_kind::SYM_relational: // relational - case symbol_kind::SYM_stringop: // stringop value.move< int > (std::move (that.value)); break; @@ -974,6 +981,18 @@ namespace yy { {} #endif +#if 201103L <= YY_CPLUSPLUS + basic_symbol (typename Base::kind_type t, CompareType&& v) + : Base (t) + , value (std::move (v)) + {} +#else + basic_symbol (typename Base::kind_type t, const CompareType& v) + : Base (t) + , value (v) + {} +#endif + #if 201103L <= YY_CPLUSPLUS basic_symbol (typename Base::kind_type t, ConstantNode*&& v) : Base (t) @@ -1227,18 +1246,6 @@ namespace yy { { } break; - case symbol_kind::SYM_equality: // equality - { } - break; - - case symbol_kind::SYM_relational: // relational - { } - break; - - case symbol_kind::SYM_stringop: // stringop - { } - break; - default: break; } @@ -1250,6 +1257,12 @@ switch (yykind) value.template destroy< AggrNode* > (); break; + case symbol_kind::SYM_equality: // equality + case symbol_kind::SYM_relational: // relational + case symbol_kind::SYM_stringop: // stringop + value.template destroy< CompareType > (); + break; + case symbol_kind::SYM_constant: // constant case symbol_kind::SYM_primary_key: // primary_key value.template destroy< ConstantNode* > (); @@ -1327,9 +1340,6 @@ switch (yykind) case symbol_kind::SYM_comp_type: // comp_type case symbol_kind::SYM_aggr_op: // aggr_op - case symbol_kind::SYM_equality: // equality - case symbol_kind::SYM_relational: // relational - case symbol_kind::SYM_stringop: // stringop value.template destroy< int > (); break; @@ -2861,6 +2871,12 @@ switch (yykind) value.copy< AggrNode* > (YY_MOVE (that.value)); break; + case symbol_kind::SYM_equality: // equality + case symbol_kind::SYM_relational: // relational + case symbol_kind::SYM_stringop: // stringop + value.copy< CompareType > (YY_MOVE (that.value)); + break; + case symbol_kind::SYM_constant: // constant case symbol_kind::SYM_primary_key: // primary_key value.copy< ConstantNode* > (YY_MOVE (that.value)); @@ -2938,9 +2954,6 @@ switch (yykind) case symbol_kind::SYM_comp_type: // comp_type case symbol_kind::SYM_aggr_op: // aggr_op - case symbol_kind::SYM_equality: // equality - case symbol_kind::SYM_relational: // relational - case symbol_kind::SYM_stringop: // stringop value.copy< int > (YY_MOVE (that.value)); break; @@ -3018,6 +3031,12 @@ switch (yykind) value.move< AggrNode* > (YY_MOVE (s.value)); break; + case symbol_kind::SYM_equality: // equality + case symbol_kind::SYM_relational: // relational + case symbol_kind::SYM_stringop: // stringop + value.move< CompareType > (YY_MOVE (s.value)); + break; + case symbol_kind::SYM_constant: // constant case symbol_kind::SYM_primary_key: // primary_key value.move< ConstantNode* > (YY_MOVE (s.value)); @@ -3095,9 +3114,6 @@ switch (yykind) case symbol_kind::SYM_comp_type: // comp_type case symbol_kind::SYM_aggr_op: // aggr_op - case symbol_kind::SYM_equality: // equality - case symbol_kind::SYM_relational: // relational - case symbol_kind::SYM_stringop: // stringop value.move< int > (YY_MOVE (s.value)); break; diff --git a/src/realm/parser/query_bison.yy b/src/realm/parser/query_bison.yy index 810733ae4f1..7fdc2742793 100644 --- a/src/realm/parser/query_bison.yy +++ b/src/realm/parser/query_bison.yy @@ -34,6 +34,8 @@ class DescriptorNode; class PropertyNode; class SubqueryNode; + + enum class CompareType: char; struct PathElem { std::string id; Mixed index; @@ -136,7 +138,8 @@ using namespace realm::query_parser; %token TYPE "@type" %token KEY_VAL "key or value" %type direction -%type equality relational stringop aggr_op +%type equality relational stringop +%type aggr_op %type coordinate %type constant primary_key %type geospatial geoloop geoloop_content geopoly_content @@ -173,6 +176,7 @@ using namespace realm::query_parser; %printer { yyo << $$.id; } ; %printer { yyo << $$; } <*>; %printer { yyo << "<>"; } <>; +%printer { yyo << string_for_op($$); } ; %% %start final; @@ -193,7 +197,7 @@ query | NOT query { $$ = drv.m_parse_nodes.create($2); } | '(' query ')' { $$ = $2; } | boolexpr { $$ =$1; } - + compare : expr equality expr { $$ = drv.m_parse_nodes.create($1, $2, $3); } | expr equality CASE expr { @@ -203,7 +207,7 @@ compare } | expr relational expr { $$ = drv.m_parse_nodes.create($1, $2, $3); } | value stringop value { $$ = drv.m_parse_nodes.create($1, $2, $3); } - | value TEXT value { $$ = drv.m_parse_nodes.create($1, CompareNode::TEXT, $3); } + | value TEXT value { $$ = drv.m_parse_nodes.create($1, CompareType::TEXT, $3); } | value stringop CASE value { auto tmp = drv.m_parse_nodes.create($1, $2, $4); tmp->case_sensitive = false; @@ -353,21 +357,21 @@ aggr_op | '.' AVG { $$ = int(AggrNode::AVG);} equality - : EQUAL { $$ = CompareNode::EQUAL; } - | NOT_EQUAL { $$ = CompareNode::NOT_EQUAL; } - | IN { $$ = CompareNode::IN; } + : EQUAL { $$ = CompareType::EQUAL; } + | NOT_EQUAL { $$ = CompareType::NOT_EQUAL; } + | IN { $$ = CompareType::IN; } relational - : LESS { $$ = CompareNode::LESS; } - | LESS_EQUAL { $$ = CompareNode::LESS_EQUAL; } - | GREATER { $$ = CompareNode::GREATER; } - | GREATER_EQUAL { $$ = CompareNode::GREATER_EQUAL; } + : LESS { $$ = CompareType::LESS; } + | LESS_EQUAL { $$ = CompareType::LESS_EQUAL; } + | GREATER { $$ = CompareType::GREATER; } + | GREATER_EQUAL { $$ = CompareType::GREATER_EQUAL; } stringop - : BEGINSWITH { $$ = CompareNode::BEGINSWITH; } - | ENDSWITH { $$ = CompareNode::ENDSWITH; } - | CONTAINS { $$ = CompareNode::CONTAINS; } - | LIKE { $$ = CompareNode::LIKE; } + : BEGINSWITH { $$ = CompareType::BEGINSWITH; } + | ENDSWITH { $$ = CompareType::ENDSWITH; } + | CONTAINS { $$ = CompareType::CONTAINS; } + | LIKE { $$ = CompareType::LIKE; } path : path_elem { $$ = drv.m_parse_nodes.create($1); } From 22fd7a39f1c7978e42818e38aa4ca25c64158901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 15 Sep 2023 22:33:26 +0200 Subject: [PATCH 14/34] Allow non-embedded links in asymmetric objects (#6981) * Allow non-embedded links in asymmetric objects * Update CHANGELOG * Add test of non-embedded links in asymmetric objects * Fix lint --- CHANGELOG.md | 3 +-- src/realm/object-store/object_schema.cpp | 7 ------- src/realm/table.cpp | 4 ---- test/object-store/schema.cpp | 12 ++++++------ test/test_table.cpp | 4 ++-- 5 files changed, 9 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f27dce29a9b..5a23cad9f6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,7 @@ # NEXT RELEASE ### Enhancements -* (PR [#????](https://github.com/realm/realm-core/pull/????)) -* None. +* Allow non-embedded links in asymmetric objects. ([PR #6981](https://github.com/realm/realm-core/pull/6981)) ### Fixed * ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?) diff --git a/src/realm/object-store/object_schema.cpp b/src/realm/object-store/object_schema.cpp index 5e9e9e8ccb9..7151a976805 100644 --- a/src/realm/object-store/object_schema.cpp +++ b/src/realm/object-store/object_schema.cpp @@ -296,13 +296,6 @@ static void validate_property(Schema const& schema, ObjectSchema const& parent_o object_name, prop.name, prop.object_type); return; } - if (parent_object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric && - it->table_type != ObjectSchema::ObjectType::Embedded) { - exceptions.emplace_back( - "Asymmetric table with property '%1.%2' of type '%3' cannot have a non-embedded object type.", - object_name, prop.name, string_for_property_type(prop.type)); - return; - } if (it->table_type == ObjectSchema::ObjectType::TopLevelAsymmetric) { exceptions.emplace_back("Property '%1.%2' of type '%3' cannot be a link to an asymmetric object.", object_name, prop.name, string_for_property_type(prop.type)); diff --git a/src/realm/table.cpp b/src/realm/table.cpp index 2db20c1a1f6..3fdbb1b4a10 100644 --- a/src/realm/table.cpp +++ b/src/realm/table.cpp @@ -415,10 +415,6 @@ ColKey Table::add_column(Table& target, StringData name) Group* target_group = target.get_parent_group(); REALM_ASSERT_RELEASE(origin_group && target_group); REALM_ASSERT_RELEASE(origin_group == target_group); - // Only links to embedded objects are allowed. - if (is_asymmetric() && !target.is_embedded()) { - throw IllegalOperation("Object property not supported in asymmetric table"); - } // Incoming links from an asymmetric table are not allowed. if (target.is_asymmetric()) { throw IllegalOperation("Ephemeral objects not supported"); diff --git a/test/object-store/schema.cpp b/test/object-store/schema.cpp index a59039b4e11..b86fba23cd7 100644 --- a/test/object-store/schema.cpp +++ b/test/object-store/schema.cpp @@ -410,16 +410,16 @@ TEST_CASE("Schema") { "Property 'object.link' of type 'object' cannot be a link to an asymmetric object.")); } - SECTION("rejects link properties with asymmetric origin object") { + SECTION("allow link properties with asymmetric origin object") { Schema schema = { {"object", ObjectSchema::ObjectType::TopLevelAsymmetric, - {{"link", PropertyType::Object | PropertyType::Nullable, "link target"}}}, - {"link target", {{"value", PropertyType::Int}}}, + {{"_id", PropertyType::Int, Property::IsPrimary{true}}, + {"link", PropertyType::Object | PropertyType::Nullable, "link target"}}}, + {"link target", + {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}}, }; - REQUIRE_EXCEPTION(schema.validate(SchemaValidationMode::SyncFLX), SchemaValidationFailed, - ContainsSubstring("Asymmetric table with property 'object.link' of type 'object' " - "cannot have a non-embedded object type.")); + REQUIRE_NOTHROW(schema.validate(SchemaValidationMode::SyncFLX)); } SECTION("allow embedded objects with asymmetric sync") { diff --git a/test/test_table.cpp b/test/test_table.cpp index 37eb26d95c8..aea5a30fc00 100644 --- a/test/test_table.cpp +++ b/test/test_table.cpp @@ -5898,8 +5898,8 @@ TEST(Table_AsymmetricObjects) tr = sg->start_write(); auto table2 = tr->add_table("target table"); table = tr->get_table("mytable"); - // Outgoing link from asymmetric object is not allowed. - CHECK_THROW(table->add_column(*table2, "link"), LogicError); + // Outgoing link from asymmetric object is allowed. + CHECK_NOTHROW(table->add_column(*table2, "link")); // Incoming link to asymmetric object is not allowed. CHECK_THROW(table2->add_column(*table, "link"), LogicError); tr->commit(); From ac64be8782a624fd0bc291f4ec6729373b85fda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Mon, 18 Sep 2023 14:20:12 +0200 Subject: [PATCH 15/34] Fix compilation of RealmTrawler --- src/realm/exec/realm_trawler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/realm/exec/realm_trawler.cpp b/src/realm/exec/realm_trawler.cpp index 873bb1efa2a..7ca56ba4c5f 100644 --- a/src/realm/exec/realm_trawler.cpp +++ b/src/realm/exec/realm_trawler.cpp @@ -1012,7 +1012,7 @@ class HistoryLogger { return true; } - bool select_collection(realm::ColKey col_key, realm::ObjKey key, const StablePath&) + bool select_collection(realm::ColKey col_key, realm::ObjKey key, const realm::StablePath&) { std::cout << "Select collection: " << m_table->get_column_name(col_key) << " on " << key << std::endl; return true; From 65148be8b0587b07221e7f35b48a735a411ac322 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Mon, 18 Sep 2023 16:00:17 +0200 Subject: [PATCH 16/34] Properly register libuv scheduler in ObjectStore tests (#6699) * Properly register libuv scheduler in ObjectStore tests * remove uv stubs in realm-fuzzer * changelog --- CHANGELOG.md | 1 + test/object-store/CMakeLists.txt | 5 +---- test/object-store/main.cpp | 10 ++++++++++ test/object-store/util/event_loop.cpp | 6 +++--- test/realm-fuzzer/CMakeLists.txt | 11 ----------- test/realm-fuzzer/fuzz_configurator.cpp | 2 ++ test/realm-fuzzer/fuzz_engine.hpp | 3 --- 7 files changed, 17 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a23cad9f6d..ec9e62cdace 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ### Internals * Update History Command tool to work with realms with fileformat v23 ([PR #6970](https://github.com/realm/realm-core/pull/6970)) +* Don't edit the ObjectStore target to enable the libuv scheduler in tests, just register the factory instead. ([PR #6699](https://github.com/realm/realm-core/pull/6699)) ---------------------------------------------- diff --git a/test/object-store/CMakeLists.txt b/test/object-store/CMakeLists.txt index 784fa015140..f249c5f011f 100644 --- a/test/object-store/CMakeLists.txt +++ b/test/object-store/CMakeLists.txt @@ -193,10 +193,7 @@ if(NOT APPLE AND NOT EMSCRIPTEN AND NOT WINDOWS_STORE) endif() target_link_libraries(ObjectStoreTests ${libuv_target}) - # FIXME: ObjectStore itself shouldn't care about this, but we need to refactor scheduler.cpp to make it happen - target_compile_definitions(ObjectStore PUBLIC REALM_HAVE_UV=1) - get_property(libuv_include_dir TARGET ${libuv_target} PROPERTY INCLUDE_DIRECTORIES) - target_include_directories(ObjectStore PRIVATE ${libuv_include_dir}) + target_compile_definitions(ObjectStoreTests PRIVATE TEST_SCHEDULER_UV=1) if (MSVC) get_target_property(comp_opts ${libuv_target} COMPILE_OPTIONS) diff --git a/test/object-store/main.cpp b/test/object-store/main.cpp index 872a9582bb1..540b38e095e 100644 --- a/test/object-store/main.cpp +++ b/test/object-store/main.cpp @@ -22,6 +22,10 @@ #include #include +#if TEST_SCHEDULER_UV +#include +#endif + #include #include #include @@ -72,6 +76,12 @@ int main(int argc, const char** argv) } } +#if TEST_SCHEDULER_UV + realm::util::Scheduler::set_default_factory([]() { + return std::make_shared(); + }); +#endif + Catch::Session session; session.useConfigData(config); int result = session.run(argc, argv); diff --git a/test/object-store/util/event_loop.cpp b/test/object-store/util/event_loop.cpp index 6463179e12e..98bc72e2858 100644 --- a/test/object-store/util/event_loop.cpp +++ b/test/object-store/util/event_loop.cpp @@ -26,7 +26,7 @@ #include #include -#if REALM_USE_UV +#if TEST_SCHEDULER_UV #include #elif REALM_PLATFORM_APPLE #include @@ -87,7 +87,7 @@ struct EventLoop::Impl { ~Impl(); private: -#if REALM_USE_UV +#if TEST_SCHEDULER_UV Impl(uv_loop_t* loop); std::vector> m_pending_work; @@ -132,7 +132,7 @@ void EventLoop::run_pending() return m_impl->run_pending(); } -#if REALM_USE_UV +#if TEST_SCHEDULER_UV bool EventLoop::has_implementation() { diff --git a/test/realm-fuzzer/CMakeLists.txt b/test/realm-fuzzer/CMakeLists.txt index 03353ff0545..7f5441beacb 100644 --- a/test/realm-fuzzer/CMakeLists.txt +++ b/test/realm-fuzzer/CMakeLists.txt @@ -32,14 +32,3 @@ if(REALM_LIBFUZZER) target_link_libraries(realm-libfuzz TestUtil ObjectStore) endif() endif() - -# on Apple platforms we use the built-in CFRunLoop -# everywhere else it's libuv, except UWP where it doesn't build -if(NOT APPLE AND NOT WINDOWS_STORE) - target_link_libraries(realm-afl++ uv_a) - if(REALM_LIBFUZZER) - if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") - target_link_libraries(realm-libfuzz uv_a) - endif() - endif() -endif() \ No newline at end of file diff --git a/test/realm-fuzzer/fuzz_configurator.cpp b/test/realm-fuzzer/fuzz_configurator.cpp index e080e77f1c1..90586ef6941 100644 --- a/test/realm-fuzzer/fuzz_configurator.cpp +++ b/test/realm-fuzzer/fuzz_configurator.cpp @@ -18,6 +18,7 @@ #include "fuzz_configurator.hpp" #include "fuzz_object.hpp" #include "../util/test_path.hpp" +#include FuzzConfigurator::FuzzConfigurator(FuzzObject& fuzzer, const std::string& input, bool use_input_file, const std::string& name) @@ -34,6 +35,7 @@ void FuzzConfigurator::setup_realm_config() { m_config.path = m_path; m_config.schema_version = 0; + m_config.scheduler = realm::util::Scheduler::make_dummy(); if (m_use_encryption) { const char* key = m_fuzzer.get_encryption_key(); const char* i = key; diff --git a/test/realm-fuzzer/fuzz_engine.hpp b/test/realm-fuzzer/fuzz_engine.hpp index 16b15fc9f5b..65d4c5c0333 100644 --- a/test/realm-fuzzer/fuzz_engine.hpp +++ b/test/realm-fuzzer/fuzz_engine.hpp @@ -25,9 +25,6 @@ #include #include #include -#if REALM_USE_UV -#include -#endif class FuzzConfigurator; class FuzzEngine { From c0a47584ef2c308bca05e33ad12120907bc98573 Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 18 Sep 2023 19:05:14 +0300 Subject: [PATCH 17/34] remove todo. fix linter --- src/realm/object-store/c_api/error.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/realm/object-store/c_api/error.cpp b/src/realm/object-store/c_api/error.cpp index 73cc1f08bcb..219de377dc8 100644 --- a/src/realm/object-store/c_api/error.cpp +++ b/src/realm/object-store/c_api/error.cpp @@ -16,7 +16,6 @@ ErrorStorage::ErrorStorage(std::exception_ptr ptr) noexcept : m_err(none) , m_message_buf() , m_usercode_error(nullptr) - { assign(std::move(ptr)); } @@ -73,7 +72,7 @@ bool ErrorStorage::operator==(const ErrorStorage& other) const noexcept else if (!m_err && !other.m_err) { return true; } - return m_err->error == other.m_err->error && m_message_buf == other.m_message_buf; // && m_usercode_error == other.m_usercode_error; //TODO: should we compare the usercode_error here + return m_err->error == other.m_err->error && m_message_buf == other.m_message_buf; } void ErrorStorage::assign(std::exception_ptr eptr) noexcept From 0779013bdbbb20af11ea3402ca2c54dff3dc6e4b Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 28 Jul 2023 11:41:59 -0700 Subject: [PATCH 18/34] Generate a v6 metadata Realm with useful data to test --- test/object-store/sync-metadata-v6.realm | Bin 0 -> 9104 bytes test/object-store/sync/metadata.cpp | 60 +++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 test/object-store/sync-metadata-v6.realm diff --git a/test/object-store/sync-metadata-v6.realm b/test/object-store/sync-metadata-v6.realm new file mode 100644 index 0000000000000000000000000000000000000000..8f3fde476bcb3dbec88e3e687b1f4af75a0f73b7 GIT binary patch literal 9104 zcmeHNJ5U=(82;}hod6+OU}LZytdq#YnMphnU>gcgWMdv;!ozQwMn0qyx>&EA#LEq2 zCX;yLhDjPqN~fdBkS61jNg9`=qsfd*N>VxhzqbqM493_a?ATsL```cE{rB5lt!{bt z%qqakjW4fUZpP2H>FGnUfxWurV>(SmndZ}YUwxIQ^)n&_RF#5f6KM<6HVfu<7CdZbgjS;B6x{DfT4 zUd(qLp2DUTTOvY!PV`eO8ROATc7VF2kMj6vBjISy2|u^R(*Vy=9Q5>`_o!P_dNEs=&Cwv_3MoBnvpbS4BsCg;n_RHX znL??Y*K)cL*l6=~De)qcW7JRdaS_FFaN%=Q0y!$<$AKRwegF&UaU7HdX-OJjIgYZL z|JpxIgh;(l&^dAc>W8buVSoir<^O#RM9gjQ~fi|fDze#?h=8XsH=^CUs6zbA^_ zm?w&uGm5w~O7?`?W5*N684Lu_#cGnskRX zZP6m0uLYe~6*sKm%sU0^$|gQj5qMC1?3X7L40%I1`H4qJJR*N){Cw3+`*AigIcdX7 zST^M}^PZkcqzh)k)N_R?tzvnC*VNOdUdklOg?sugn|h-ScM(_6MlG4t@r!#8cR0%X zZ|EiAQbC(aWv#4Nh$@(qDSgUF>iAt;A>Lw+WB<~4C`jyawm52ykEv_fT%xFzGq!H{ zXq<6EdaQ`FDfvO=;pd@6;EQ^huOEw2P1xc7C$A1{L|R^KAcT<3>hQYk}=lb-7%h^`$=zcm|gzm`e$_ZlPll9B4~ zHpgyv8Y9Nw?AYzG*|GR=AvSPrN^8G08N1SJq~qa>XM4-t1AXOm_qC4x-l0q3SgdCx z-8XbAe&@oydpF{dX|wa&p4m(+eXi>!CQ|J@!UxYI_BQ5mICw03-Vr<+tDVQ;-~lbw z&f{>_$N6xz^Eezl&RwW>9*2a-c(wC596UPH)z0H^@DS&}|NHm(#%J#a9>tF-51pO+ z9(juOU0(35`WBAF{c6M0hULbYqtRpA=fOI%lQbBI-9Bf6E1xWUUhQ$&Z@r20-~>Oz zO$=Qh>`q_3c(C(Y_2=n+>+ShGjJGn64|p8tcL008`X+zRJU_2qWqrK)-!sqW@jkDQ z4|u%n@BI(^?m_me>VFS@`2ILJ9@cp+-lQIXdVINgrX_l!=H!!;i>K~>bf$Hqb^E-A z@7H90J;vTY;j@gd^;w~F+S{(j_mB8Ka{ctiX$bfOY9Jhl21WyS1I566U?H%b2j=DN z`}roOW9@^r%+Jz`n)2OCj`uS%y_Cm09f{HQt$TAzjy#ih*yQhty@f-Ks;XV;sG3pd z)kSq#T~*iBP1PT?MwrGI?dnf~>RIqCd1{)%O)ICD0;_?wz((Lqkn~Z!w;+mh&?D}p z>=f^mz%HW}$Pe!($dAe#t6zeOd%gYaDL(e!*M*a0G*}D8pakE+OmI1PikfG4B1Fj! z=bN;1UE#f#wC8_fkP|>1`5~2vVji^gqBLQN$99#j&_JjdLIKRMn&fzAC-LRX?C8C^ z{R*JsO}?IDb{LmJv%=#$mO{&+HH^%8{6CJTTkS;Ap5%zjzW>MZ0KGOt9;j)PpaJ}C uY8xT8-5`&zWTXK{)Yanlhr(^7=W<62#M`8PfFhIP9pk6}T)5WQ8U7E=Zo|s} literal 0 HcmV?d00001 diff --git a/test/object-store/sync/metadata.cpp b/test/object-store/sync/metadata.cpp index e0eda4d8ce4..a868579a076 100644 --- a/test/object-store/sync/metadata.cpp +++ b/test/object-store/sync/metadata.cpp @@ -504,14 +504,74 @@ TEST_CASE("sync metadata: can open old metadata realms", "[sync][metadata]") { const auto identity = "metadata migration test"; const std::string sample_token = "metadata migration token"; + const auto access_token_1 = encode_fake_jwt("access token 1", 456, 123); + const auto access_token_2 = encode_fake_jwt("access token 2", 456, 124); + const auto refresh_token_1 = encode_fake_jwt("refresh token 1", 456, 123); + const auto refresh_token_2 = encode_fake_jwt("refresh token 2", 456, 124); + // change to true to create a test file for the current schema version // this will only work on unix-like systems if ((false)) { +#if true + // Code to generate the v6 metadata Realm used to test the 6 -> 7 migration + { + using State = SyncUser::State; + SyncMetadataManager manager(metadata_path, false); + + auto user = manager.get_or_make_user_metadata("removed user", ""); + user->set_state(State::Removed); + + auto make_user_pair = [&](const char* name, State state1, State state2, const std::string& token_1, + const std::string& token_2) { + auto user = manager.get_or_make_user_metadata(name, "a"); + user->set_state_and_tokens(state1, token_1, refresh_token_1); + user->set_identities({{"identity 1", "a"}, {"shared identity", "shared"}}); + user->add_realm_file_path("file 1"); + user->add_realm_file_path("file 2"); + + user = manager.get_or_make_user_metadata(name, "b"); + user->set_state_and_tokens(state2, token_2, refresh_token_2); + user->set_identities({{"identity 2", "b"}, {"shared identity", "shared"}}); + user->add_realm_file_path("file 2"); + user->add_realm_file_path("file 3"); + }; + + make_user_pair("first logged in, second logged out", State::LoggedIn, State::LoggedOut, access_token_1, + access_token_2); + make_user_pair("first logged in, second removed", State::LoggedIn, State::Removed, access_token_1, + access_token_2); + make_user_pair("second logged in, first logged out", State::LoggedOut, State::LoggedIn, access_token_1, + access_token_2); + make_user_pair("second logged in, first removed", State::Removed, State::LoggedIn, access_token_1, + access_token_2); + make_user_pair("both logged in, first newer", State::LoggedIn, State::LoggedIn, access_token_2, + access_token_1); + make_user_pair("both logged in, second newer", State::LoggedIn, State::LoggedIn, access_token_1, + access_token_2); + } + + // Replace the randomly generated UUIDs with deterministic values + { + Realm::Config config; + config.path = metadata_path; + auto realm = Realm::get_shared_realm(config); + realm->begin_transaction(); + auto& group = realm->read_group(); + auto table = group.get_table("class_UserMetadata"); + auto col = table->get_column_key("local_uuid"); + size_t i = 0; + for (auto& obj : *table) { + obj.set(col, util::to_string(i++)); + } + realm->commit_transaction(); + } +#else { // Create a metadata Realm with a test user SyncMetadataManager manager(metadata_path, false); auto user_metadata = manager.get_or_make_user_metadata(identity, provider_type); user_metadata->set_access_token(sample_token); } +#endif // Open the metadata Realm directly and grab the schema version from it Realm::Config config; From f486ffa0778239c99e52d7c19e61242b3ea0b5e0 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 22 Dec 2021 14:00:40 -0800 Subject: [PATCH 19/34] Extract duplicated code in SyncUserProfile --- src/realm/object-store/sync/sync_user.hpp | 56 ++++++++--------------- 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/src/realm/object-store/sync/sync_user.hpp b/src/realm/object-store/sync/sync_user.hpp index 1bd5f9e8fca..f7a5216fbcb 100644 --- a/src/realm/object-store/sync/sync_user.hpp +++ b/src/realm/object-store/sync/sync_user.hpp @@ -75,74 +75,47 @@ struct SyncUserProfile { // The full name of the user. util::Optional name() const { - if (m_data.find("name") == m_data.end()) { - return util::none; - } - return static_cast(m_data.at("name")); + return get_field("name"); } // The email address of the user. util::Optional email() const { - if (m_data.find("email") == m_data.end()) { - return util::none; - } - return static_cast(m_data.at("email")); + return get_field("email"); } // A URL to the user's profile picture. util::Optional picture_url() const { - if (m_data.find("picture_url") == m_data.end()) { - return util::none; - } - return static_cast(m_data.at("picture_url")); + return get_field("picture_url"); } // The first name of the user. util::Optional first_name() const { - if (m_data.find("first_name") == m_data.end()) { - return util::none; - } - return static_cast(m_data.at("first_name")); + return get_field("first_name"); } // The last name of the user. util::Optional last_name() const { - if (m_data.find("last_name") == m_data.end()) { - return util::none; - } - return static_cast(m_data.at("last_name")); + return get_field("last_name"); } // The gender of the user. util::Optional gender() const { - if (m_data.find("gender") == m_data.end()) { - return util::none; - } - return static_cast(m_data.at("gender")); + return get_field("gender"); } // The birthdate of the user. util::Optional birthday() const { - if (m_data.find("birthday") == m_data.end()) { - return util::none; - } - return static_cast(m_data.at("birthday")); + return get_field("birthday"); } // The minimum age of the user. util::Optional min_age() const { - if (m_data.find("min_age") == m_data.end()) { - return util::none; - } - return static_cast(m_data.at("min_age")); + return get_field("min_age"); } // The maximum age of the user. util::Optional max_age() const { - if (m_data.find("max_age") == m_data.end()) { - return util::none; - } - return static_cast(m_data.at("max_age")); + return get_field("max_age"); } bson::Bson operator[](const std::string& key) const @@ -150,7 +123,7 @@ struct SyncUserProfile { return m_data.at(key); } - bson::BsonDocument data() const + const bson::BsonDocument& data() const { return m_data; } @@ -163,6 +136,15 @@ struct SyncUserProfile { private: bson::BsonDocument m_data; + + util::Optional get_field(const char* name) const + { + auto it = m_data.find(name); + if (it == m_data.end()) { + return util::none; + } + return static_cast((*it).second); + } }; // A struct that represents an identity that a `User` is linked to From 5f02d99b32d0d84422ad24ceeeb298aa9e1604ca Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Tue, 25 Jul 2023 17:43:44 -0700 Subject: [PATCH 20/34] Fix handling of users with multiple identities ROS users were identified by the pair (identity, auth_url), and in the v10 port this was translated to (user_id, provider_type), but that isn't actually how Atlas users work. An App is the equivalent of the old auth_url, and within an App user_id uniquely identifies a user. Users don't actually have a "provider type" at all: users have one or more identities, each of which has a provider type, and the same SyncUser should be used regardless of which identity was used to log in. This had surprisingly mild consequences, but was visibly broken in a few ways. Logging into the same user using two different identities resulted in two SyncUsers with the same user id. Opening the same partition (or any flx Realm) with both users resulted in both using whichever SyncUser happened to open it first, and the second would not create a SyncSession. This meant that most of the potential bad things (such as creating two session for one file) didn't actually happen, but the second user's list of sessions would be "incorrect", and removing one of the users would remove the incorrect set of local files. Linking identities to anonymous users resulted in the user still being treated as anonymous. The primary negative effect of this was that linking an identity, logging out, then logging back in would result in the user being removed entirely in between, forcing the re-download of all Realms (and the loss of any un-uploaded data). This bumps the schema version of the metadata Realm primarily for the sake of recovery on metadata Realms in invalid states: the migration block handles the case of multiple users with the same user id and unifies them. Since this needs a migration anyway, it fixes some incidental problems with the metadata Realm's schema which weren't fixable without a migration: the marked_for_deletion column was redundant with the Removed state, identity objects were leaked due to not being embedded, and the local_uuid field is no longer used for anything other than opening files written by early v10 versions, so there's no need to populate it for new users. --- CHANGELOG.md | 12 +- src/realm.h | 4 - src/realm/collection.hpp | 2 +- src/realm/object-store/c_api/app.cpp | 10 - src/realm/object-store/sync/app.cpp | 11 +- .../object-store/sync/impl/sync_file.cpp | 27 +- .../object-store/sync/impl/sync_file.hpp | 7 +- .../object-store/sync/impl/sync_metadata.cpp | 351 +++++++++++------- .../object-store/sync/impl/sync_metadata.hpp | 31 +- src/realm/object-store/sync/sync_manager.cpp | 41 +- src/realm/object-store/sync/sync_manager.hpp | 3 +- src/realm/object-store/sync/sync_user.cpp | 63 ++-- src/realm/object-store/sync/sync_user.hpp | 37 +- test/object-store/CMakeLists.txt | 6 +- test/object-store/c_api/c_api.cpp | 178 ++------- test/object-store/sync/app.cpp | 321 ++++------------ test/object-store/sync/file.cpp | 43 ++- test/object-store/sync/flx_sync.cpp | 2 +- test/object-store/sync/metadata.cpp | 185 ++++----- .../connection_change_notifications.cpp | 6 +- test/object-store/sync/session/session.cpp | 4 +- .../sync/session/wait_for_completion.cpp | 18 +- test/object-store/sync/sync_manager.cpp | 143 +++---- test/object-store/sync/user.cpp | 61 ++- .../util/sync/sync_test_utils.cpp | 11 +- .../util/sync/sync_test_utils.hpp | 5 +- test/object-store/util/test_file.cpp | 3 +- .../object-store/util/unit_test_transport.cpp | 228 ++++++++++++ .../object-store/util/unit_test_transport.hpp | 83 +++++ 29 files changed, 959 insertions(+), 937 deletions(-) create mode 100644 test/object-store/util/unit_test_transport.cpp create mode 100644 test/object-store/util/unit_test_transport.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index ec9e62cdace..41bcc3f3b7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,21 @@ ### Fixed * ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?) -* None. +* Logging into a single user using multiple auth providers created a separate SyncUser per auth provider. This mostly worked, but had some quirks: + - Sync sessions would not necessarily be associated with the specific SyncUser used to create them. As a result, querying a user for its sessions could give incorrect results, and logging one user out could close the wrong sessions. + - Existing local synchronized Realm files created using version of Realm from August - November 2020 would sometimes not be opened correctly and would instead be redownloaded. + - Removing one of the SyncUsers would delete all local Realm files for all SyncUsers for that user. + - Deleting the server-side user via one of the SyncUsers left the other SyncUsers in an invalid state. + - A SyncUser which was originally created via anonymous login and then linked to an identity would still be treated as an anonymous users and removed entirely on logout. + (since v10.0.0) ### Breaking changes -* None. +* SyncUser::provider_type() and realm_user_get_auth_provider() have been removed. Users don't have provider types; identities do. +* SyncUser no longer has a `local_identity()`. `identity()` has been guaranteed to be unique per App ever since v10. ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. +* The metadata Realm used to store sync users has had its schema version bumped. It is automatically migrated to the new version on first open. Downgrading to older version of Realm after upgrading will discard stored user tokens and require logging back in. ----------- diff --git a/src/realm.h b/src/realm.h index 5c68057c121..e05f99f3df0 100644 --- a/src/realm.h +++ b/src/realm.h @@ -3232,13 +3232,9 @@ RLM_API realm_user_state_e realm_user_get_state(const realm_user_t* user) RLM_AP RLM_API bool realm_user_get_all_identities(const realm_user_t* user, realm_user_identity_t* out_identities, size_t capacity, size_t* out_n); -RLM_API const char* realm_user_get_local_identity(const realm_user_t*) RLM_API_NOEXCEPT; - // returned pointer must be manually released with realm_free() RLM_API char* realm_user_get_device_id(const realm_user_t*) RLM_API_NOEXCEPT; -RLM_API realm_auth_provider_e realm_user_get_auth_provider(const realm_user_t*) RLM_API_NOEXCEPT; - /** * Log out the user and mark it as logged out. * diff --git a/src/realm/collection.hpp b/src/realm/collection.hpp index 297c8fc1a61..d0cde6961c7 100644 --- a/src/realm/collection.hpp +++ b/src/realm/collection.hpp @@ -19,7 +19,7 @@ struct CollectionIterator; /// Collections are bound to particular properties of an object. In a /// collection's public interface, the implementation must take care to keep the /// object consistent with the persisted state, mindful of the fact that the -/// state may have changed as a consquence of modifications from other instances +/// state may have changed as a consequence of modifications from other instances /// referencing the same persisted state. class CollectionBase { public: diff --git a/src/realm/object-store/c_api/app.cpp b/src/realm/object-store/c_api/app.cpp index 2c520ed46d3..6d0568a0d2f 100644 --- a/src/realm/object-store/c_api/app.cpp +++ b/src/realm/object-store/c_api/app.cpp @@ -642,11 +642,6 @@ RLM_API bool realm_user_get_all_identities(const realm_user_t* user, realm_user_ }); } -RLM_API const char* realm_user_get_local_identity(const realm_user_t* user) noexcept -{ - return (*user)->local_identity().c_str(); -} - RLM_API char* realm_user_get_device_id(const realm_user_t* user) noexcept { if ((*user)->has_device_id()) { @@ -656,11 +651,6 @@ RLM_API char* realm_user_get_device_id(const realm_user_t* user) noexcept return nullptr; } -RLM_API realm_auth_provider_e realm_user_get_auth_provider(const realm_user_t* user) noexcept -{ - return realm_auth_provider_e(enum_from_provider_type((*user)->provider_type())); -} - RLM_API bool realm_user_log_out(realm_user_t* user) { return wrap_err([&] { diff --git a/src/realm/object-store/sync/app.cpp b/src/realm/object-store/sync/app.cpp index 54da64a8674..f5632871e23 100644 --- a/src/realm/object-store/sync/app.cpp +++ b/src/realm/object-store/sync/app.cpp @@ -644,7 +644,7 @@ void App::log_in_with_credentials( // is already an anonymous session active, reuse it if (credentials.provider() == AuthProvider::ANONYMOUS) { for (auto&& user : m_sync_manager->all_users()) { - if (user->provider_type() == credentials.provider_as_string() && user->is_logged_in()) { + if (user->is_anonymous()) { completion(switch_user(user), util::none); return; } @@ -686,8 +686,7 @@ void App::log_in_with_credentials( else { sync_user = self->m_sync_manager->get_user( get(json, "user_id"), get(json, "refresh_token"), - get(json, "access_token"), credentials.provider_as_string(), - get(json, "device_id")); + get(json, "access_token"), get(json, "device_id")); } } catch (const AppError& e) { @@ -758,11 +757,7 @@ std::shared_ptr App::switch_user(const std::shared_ptr& user if (!user || user->state() != SyncUser::State::LoggedIn) { throw AppError(ErrorCodes::ClientUserNotLoggedIn, "User is no longer valid or is logged out"); } - - auto users = m_sync_manager->all_users(); - auto it = std::find(users.begin(), users.end(), user); - - if (it == users.end()) { + if (!verify_user_present(user)) { throw AppError(ErrorCodes::ClientUserNotFound, "User does not exist"); } diff --git a/src/realm/object-store/sync/impl/sync_file.cpp b/src/realm/object-store/sync/impl/sync_file.cpp index 6e6e075b7af..c59a2e2b321 100644 --- a/src/realm/object-store/sync/impl/sync_file.cpp +++ b/src/realm/object-store/sync/impl/sync_file.cpp @@ -295,11 +295,11 @@ bool SyncFileManager::copy_realm_file(const std::string& old_path, const std::st return true; } -bool SyncFileManager::remove_realm(const std::string& user_identity, const std::string& local_identity, +bool SyncFileManager::remove_realm(const std::string& user_identity, + const std::vector& legacy_user_identities, const std::string& raw_realm_path, const std::string& partition) const { - util::Optional existing = - get_existing_realm_file_path(user_identity, local_identity, raw_realm_path, partition); + auto existing = get_existing_realm_file_path(user_identity, legacy_user_identities, raw_realm_path, partition); if (existing) { return remove_realm(*existing); } @@ -327,10 +327,10 @@ static bool try_file_remove(const std::string& path) noexcept } } -util::Optional SyncFileManager::get_existing_realm_file_path(const std::string& user_identity, - const std::string& local_user_identity, - const std::string& realm_file_name, - const std::string& partition) const +util::Optional +SyncFileManager::get_existing_realm_file_path(const std::string& user_identity, + const std::vector& legacy_user_identities, + const std::string& realm_file_name, const std::string& partition) const { std::string preferred_name = preferred_realm_path_without_suffix(user_identity, realm_file_name); if (try_file_exists(preferred_name)) { @@ -365,14 +365,14 @@ util::Optional SyncFileManager::get_existing_realm_file_path(const } } - if (!local_user_identity.empty()) { + for (auto& legacy_identity : legacy_user_identities) { // retain support for legacy paths - std::string old_path = legacy_realm_file_path(local_user_identity, realm_file_name); + std::string old_path = legacy_realm_file_path(legacy_identity, realm_file_name); if (try_file_exists(old_path)) { return old_path; } // retain support for legacy local identity paths - std::string old_local_identity_path = legacy_local_identity_path(local_user_identity, partition); + std::string old_local_identity_path = legacy_local_identity_path(legacy_identity, partition); if (try_file_exists(old_local_identity_path)) { return old_local_identity_path; } @@ -381,11 +381,12 @@ util::Optional SyncFileManager::get_existing_realm_file_path(const return util::none; } -std::string SyncFileManager::realm_file_path(const std::string& user_identity, const std::string& local_user_identity, +std::string SyncFileManager::realm_file_path(const std::string& user_identity, + const std::vector& legacy_user_identities, const std::string& realm_file_name, const std::string& partition) const { - util::Optional existing_path = - get_existing_realm_file_path(user_identity, local_user_identity, realm_file_name, partition); + auto existing_path = + get_existing_realm_file_path(user_identity, legacy_user_identities, realm_file_name, partition); if (existing_path) { return *existing_path; } diff --git a/src/realm/object-store/sync/impl/sync_file.hpp b/src/realm/object-store/sync/impl/sync_file.hpp index 455f03f640d..7750ae85748 100644 --- a/src/realm/object-store/sync/impl/sync_file.hpp +++ b/src/realm/object-store/sync/impl/sync_file.hpp @@ -69,15 +69,16 @@ class SyncFileManager { static bool try_file_exists(const std::string& path) noexcept; util::Optional get_existing_realm_file_path(const std::string& user_identity, - const std::string& local_user_identity, + const std::vector& legacy_user_identities, const std::string& realm_file_name, const std::string& partition) const; /// Return the path for a given Realm, creating the user directory if it does not already exist. - std::string realm_file_path(const std::string& user_identity, const std::string& local_user_identity, + std::string realm_file_path(const std::string& user_identity, + const std::vector& legacy_user_identities, const std::string& realm_file_name, const std::string& partition) const; /// Remove the Realm at a given path for a given user. Returns `true` if the remove operation fully succeeds. - bool remove_realm(const std::string& user_identity, const std::string& local_identity, + bool remove_realm(const std::string& user_identity, const std::vector& legacy_user_identities, const std::string& realm_file_name, const std::string& partition) const; /// Remove the Realm whose primary Realm file is located at `absolute_path`. Returns `true` if the remove diff --git a/src/realm/object-store/sync/impl/sync_metadata.cpp b/src/realm/object-store/sync/impl/sync_metadata.cpp index bb84e02fce6..8fc53c30da6 100644 --- a/src/realm/object-store/sync/impl/sync_metadata.cpp +++ b/src/realm/object-store/sync/impl/sync_metadata.cpp @@ -34,6 +34,8 @@ #include #include +using namespace realm; + namespace { static const char* const c_sync_userMetadata = "UserMetadata"; static const char* const c_sync_identityMetadata = "UserIdentity"; @@ -41,10 +43,9 @@ static const char* const c_sync_app_metadata = "AppMetadata"; static const char* const c_sync_current_user_identity = "current_user_identity"; -/* User keys*/ -static const char* const c_sync_marked_for_removal = "marked_for_removal"; +/* User keys */ static const char* const c_sync_identity = "identity"; -static const char* const c_sync_local_uuid = "local_uuid"; +static const char* const c_sync_legacy_uuids = "legacy_uuids"; static const char* const c_sync_refresh_token = "refresh_token"; static const char* const c_sync_access_token = "access_token"; static const char* const c_sync_identities = "identities"; @@ -61,7 +62,7 @@ static const char* const c_sync_fileActionMetadata = "FileActionMetadata"; static const char* const c_sync_original_name = "original_name"; static const char* const c_sync_new_name = "new_name"; static const char* const c_sync_action = "action"; -static const char* const c_sync_url = "url"; +static const char* const c_sync_partition = "url"; static const char* const c_sync_app_metadata_id = "id"; static const char* const c_sync_app_metadata_deployment_model = "deployment_model"; @@ -72,47 +73,148 @@ static const char* const c_sync_app_metadata_ws_hostname = "ws_hostname"; realm::Schema make_schema() { using namespace realm; - return Schema{{c_sync_identityMetadata, - {{c_sync_user_id, PropertyType::String}, {c_sync_provider_type, PropertyType::String}}}, - {c_sync_userMetadata, - {{c_sync_identity, PropertyType::String}, - {c_sync_local_uuid, PropertyType::String}, - {c_sync_marked_for_removal, PropertyType::Bool}, - {c_sync_refresh_token, PropertyType::String | PropertyType::Nullable}, - {c_sync_provider_type, PropertyType::String}, - {c_sync_access_token, PropertyType::String | PropertyType::Nullable}, - {c_sync_identities, PropertyType::Object | PropertyType::Array, c_sync_identityMetadata}, - {c_sync_state, PropertyType::Int}, - {c_sync_device_id, PropertyType::String}, - {c_sync_profile_data, PropertyType::String}, - {c_sync_local_realm_paths, PropertyType::Set | PropertyType::String}}}, - {c_sync_fileActionMetadata, - { - {c_sync_original_name, PropertyType::String, Property::IsPrimary{true}}, - {c_sync_new_name, PropertyType::String | PropertyType::Nullable}, - {c_sync_action, PropertyType::Int}, - {c_sync_url, PropertyType::String}, - {c_sync_identity, PropertyType::String}, - }}, - {c_sync_current_user_identity, {{c_sync_current_user_identity, PropertyType::String}}}, - {c_sync_app_metadata, - {{c_sync_app_metadata_id, PropertyType::Int, Property::IsPrimary{true}}, - {c_sync_app_metadata_deployment_model, PropertyType::String}, - {c_sync_app_metadata_location, PropertyType::String}, - {c_sync_app_metadata_hostname, PropertyType::String}, - {c_sync_app_metadata_ws_hostname, PropertyType::String}}}}; + return Schema{ + {c_sync_identityMetadata, + ObjectSchema::ObjectType::Embedded, + { + {c_sync_user_id, PropertyType::String}, + {c_sync_provider_type, PropertyType::String}, + }}, + {c_sync_userMetadata, + {{c_sync_identity, PropertyType::String}, + {c_sync_legacy_uuids, PropertyType::String | PropertyType::Array}, + {c_sync_refresh_token, PropertyType::String | PropertyType::Nullable}, + {c_sync_access_token, PropertyType::String | PropertyType::Nullable}, + {c_sync_identities, PropertyType::Object | PropertyType::Array, c_sync_identityMetadata}, + {c_sync_state, PropertyType::Int}, + {c_sync_device_id, PropertyType::String}, + {c_sync_profile_data, PropertyType::String}, + {c_sync_local_realm_paths, PropertyType::Set | PropertyType::String}}}, + {c_sync_fileActionMetadata, + { + {c_sync_original_name, PropertyType::String, Property::IsPrimary{true}}, + {c_sync_new_name, PropertyType::String | PropertyType::Nullable}, + {c_sync_action, PropertyType::Int}, + {c_sync_partition, PropertyType::String}, + {c_sync_identity, PropertyType::String}, + }}, + {c_sync_current_user_identity, + { + {c_sync_current_user_identity, PropertyType::String}, + }}, + {c_sync_app_metadata, + { + {c_sync_app_metadata_id, PropertyType::Int, Property::IsPrimary{true}}, + {c_sync_app_metadata_deployment_model, PropertyType::String}, + {c_sync_app_metadata_location, PropertyType::String}, + {c_sync_app_metadata_hostname, PropertyType::String}, + {c_sync_app_metadata_ws_hostname, PropertyType::String}, + }}, + }; } -} // anonymous namespace +void migrate_to_v7(std::shared_ptr old_realm, std::shared_ptr realm) +{ + // Before schema version 7 there may have been multiple UserMetadata entries + // for a single user_id with different provider types, so we need to merge + // any duplicates together -namespace realm { + TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata); + TableRef old_table = ObjectStore::table_for_object_type(old_realm->read_group(), c_sync_userMetadata); + if (table->is_empty()) + return; + REALM_ASSERT(table->size() == old_table->size()); + + ColKey id_col = table->get_column_key(c_sync_identity); + ColKey old_uuid_col = old_table->get_column_key("local_uuid"); + ColKey new_uuid_col = table->get_column_key(c_sync_legacy_uuids); + ColKey state_col = table->get_column_key(c_sync_state); + + std::unordered_map users; + for (size_t i = 0, j = 0; i < table->size(); ++j) { + auto obj = table->get_object(i); + + // Move the local uuid from the old column to the list + auto old_obj = old_table->get_object(j); + obj.get_list(new_uuid_col).add(old_obj.get(old_uuid_col)); + + // Check if we've already seen an object with the same id. If not, store + // this one and move on + std::string user_id = obj.get(id_col); + auto& existing = users[obj.get(id_col)]; + if (!existing.is_valid()) { + existing = obj; + ++i; + continue; + } + + // We have a second object for the same id, so we need to merge them. + // First we merge the state: if one is logged in and the other isn't, + // we'll use the logged-in state and tokens. If both are logged in, we'll + // use the more recent login. If one is logged out and the other is + // removed we'll use the logged out state. If both are logged out or + // both are removed then it doesn't matter which we pick. + using State = SyncUser::State; + auto state = State(obj.get(state_col)); + auto existing_state = State(existing.get(state_col)); + if (state == existing_state) { + if (state == State::LoggedIn) { + RealmJWT token_1(existing.get(c_sync_access_token)); + RealmJWT token_2(obj.get(c_sync_access_token)); + if (token_1.issued_at < token_2.issued_at) { + existing.set(c_sync_refresh_token, obj.get(c_sync_refresh_token)); + existing.set(c_sync_access_token, obj.get(c_sync_access_token)); + } + } + } + else if (state == State::LoggedIn || existing_state == State::Removed) { + existing.set(c_sync_state, int64_t(state)); + existing.set(c_sync_refresh_token, obj.get(c_sync_refresh_token)); + existing.set(c_sync_access_token, obj.get(c_sync_access_token)); + } + + // Next we merge the list properties (identities, legacy uuids, realm file paths) + { + auto dest = existing.get_linklist(c_sync_identities); + auto src = obj.get_linklist(c_sync_identities); + for (size_t i = 0, size = src.size(); i < size; ++i) { + if (dest.find_first(src.get(i)) == npos) { + dest.add(src.get(i)); + } + } + } + { + auto dest = existing.get_list(c_sync_legacy_uuids); + auto src = obj.get_list(c_sync_legacy_uuids); + for (size_t i = 0, size = src.size(); i < size; ++i) { + if (dest.find_first(src.get(i)) == npos) { + dest.add(src.get(i)); + } + } + } + { + auto dest = existing.get_set(c_sync_local_realm_paths); + auto src = obj.get_set(c_sync_local_realm_paths); + for (size_t i = 0, size = src.size(); i < size; ++i) { + dest.insert(src.get(i)); + } + } + + + // Finally we delete the duplicate object. We don't increment `i` as it's + // now the index of the object just after the one we're deleting. + obj.remove(); + } +} + +} // anonymous namespace // MARK: - Sync metadata manager SyncMetadataManager::SyncMetadataManager(std::string path, bool should_encrypt, util::Optional> encryption_key) { - constexpr uint64_t SCHEMA_VERSION = 6; + constexpr uint64_t SCHEMA_VERSION = 7; if (!REALM_PLATFORM_APPLE && should_encrypt && !encryption_key) throw InvalidArgument("Metadata Realm encryption was specified, but no encryption key was provided."); @@ -125,6 +227,13 @@ SyncMetadataManager::SyncMetadataManager(std::string path, bool should_encrypt, m_metadata_config.scheduler = util::Scheduler::make_dummy(); if (encryption_key) m_metadata_config.encryption_key = std::move(*encryption_key); + m_metadata_config.automatically_handle_backlinks_in_migrations = true; + m_metadata_config.migration_function = [](std::shared_ptr old_realm, std::shared_ptr realm, + Schema&) { + if (old_realm->schema_version() < 7) { + migrate_to_v7(old_realm, realm); + } + }; auto realm = open_realm(should_encrypt, encryption_key != none); @@ -135,8 +244,7 @@ SyncMetadataManager::SyncMetadataManager(std::string path, bool should_encrypt, object_schema->persisted_properties[2].column_key, object_schema->persisted_properties[3].column_key, object_schema->persisted_properties[4].column_key, object_schema->persisted_properties[5].column_key, object_schema->persisted_properties[6].column_key, object_schema->persisted_properties[7].column_key, - object_schema->persisted_properties[8].column_key, object_schema->persisted_properties[9].column_key, - object_schema->persisted_properties[10].column_key}; + object_schema->persisted_properties[8].column_key}; object_schema = realm->schema().find(c_sync_fileActionMetadata); m_file_action_schema = { @@ -145,8 +253,6 @@ SyncMetadataManager::SyncMetadataManager(std::string path, bool should_encrypt, object_schema->persisted_properties[4].column_key, }; - object_schema = realm->schema().find(c_sync_current_user_identity); - object_schema = realm->schema().find(c_sync_app_metadata); m_app_metadata_schema = { object_schema->persisted_properties[0].column_key, object_schema->persisted_properties[1].column_key, @@ -168,7 +274,13 @@ SyncUserMetadataResults SyncMetadataManager::get_users(bool marked) const { auto realm = get_realm(); TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata); - Query query = table->where().equal(m_user_schema.marked_for_removal_col, marked); + Query query; + if (marked) { + query = table->where().equal(m_user_schema.state_col, int64_t(SyncUser::State::Removed)); + } + else { + query = table->where().not_equal(m_user_schema.state_col, int64_t(SyncUser::State::Removed)); + } return SyncUserMetadataResults(Results(realm, std::move(query)), m_user_schema); } @@ -213,7 +325,6 @@ void SyncMetadataManager::set_current_user_identity(const std::string& identity) } util::Optional SyncMetadataManager::get_or_make_user_metadata(const std::string& identity, - const std::string& provider_type, bool make_if_absent) const { auto realm = get_realm(); @@ -221,79 +332,54 @@ util::Optional SyncMetadataManager::get_or_make_user_metadata( // Retrieve or create the row for this object. TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata); - Query query = table->where() - .equal(schema.identity_col, StringData(identity)) - .equal(schema.provider_type_col, StringData(provider_type)); + Query query = table->where().equal(schema.identity_col, StringData(identity)); Results results(realm, std::move(query)); REALM_ASSERT_DEBUG(results.size() < 2); - auto row = results.first(); + auto obj = results.first(); - if (!row) { + if (!obj) { if (!make_if_absent) return none; realm->begin_transaction(); // Check the results again. - row = results.first(); - if (!row) { - // Because "making this user" is our last action, set this new user as the current user - TableRef currentUserIdentityTable = - ObjectStore::table_for_object_type(realm->read_group(), c_sync_current_user_identity); - - Obj currentUserIdentityObj; - if (currentUserIdentityTable->is_empty()) - currentUserIdentityObj = currentUserIdentityTable->create_object(); - else - currentUserIdentityObj = *currentUserIdentityTable->begin(); - - auto obj = table->create_object(); - - currentUserIdentityObj.set(c_sync_current_user_identity, identity); - - std::string uuid = util::uuid_string(); - obj.set(schema.identity_col, identity); - obj.set(schema.provider_type_col, provider_type); - obj.set(schema.local_uuid_col, uuid); - obj.set(schema.marked_for_removal_col, false); - obj.set(schema.state_col, (int64_t)SyncUser::State::LoggedIn); - realm->commit_transaction(); - return SyncUserMetadata(schema, std::move(realm), std::move(obj)); - } - else { - // Someone beat us to adding this user. - if (row->get(schema.marked_for_removal_col)) { - // User is dead. Revive or return none. - if (make_if_absent) { - row->set(schema.marked_for_removal_col, false); - realm->commit_transaction(); - } - else { - realm->cancel_transaction(); - return none; - } - } - else { - // User is alive, nothing else to do. - realm->cancel_transaction(); - } - return SyncUserMetadata(schema, std::move(realm), std::move(*row)); - } + obj = results.first(); + } + if (!obj) { + // Because "making this user" is our last action, set this new user as the current user + TableRef currentUserIdentityTable = + ObjectStore::table_for_object_type(realm->read_group(), c_sync_current_user_identity); + + Obj currentUserIdentityObj; + if (currentUserIdentityTable->is_empty()) + currentUserIdentityObj = currentUserIdentityTable->create_object(); + else + currentUserIdentityObj = *currentUserIdentityTable->begin(); + + obj = table->create_object(); + + currentUserIdentityObj.set(c_sync_current_user_identity, identity); + + obj->set(schema.identity_col, identity); + obj->set(schema.state_col, (int64_t)SyncUser::State::LoggedIn); + realm->commit_transaction(); + return SyncUserMetadata(schema, std::move(realm), *obj); } // Got an existing user. - if (row->get(schema.marked_for_removal_col)) { + if (obj->get(schema.state_col) == int64_t(SyncUser::State::Removed)) { // User is dead. Revive or return none. - if (make_if_absent) { - realm->begin_transaction(); - row->set(schema.marked_for_removal_col, false); - realm->commit_transaction(); - } - else { + if (!make_if_absent) { return none; } + + if (!realm->is_in_transaction()) + realm->begin_transaction(); + obj->set(schema.state_col, (int64_t)SyncUser::State::LoggedIn); + realm->commit_transaction(); } - return SyncUserMetadata(schema, std::move(realm), std::move(*row)); + return SyncUserMetadata(schema, std::move(realm), std::move(*obj)); } void SyncMetadataManager::make_file_action_metadata(StringData original_name, StringData partition_key_value, @@ -318,7 +404,7 @@ void SyncMetadataManager::make_file_action_metadata(StringData original_name, St obj.set(schema.idx_new_name, new_name); obj.set(schema.idx_action, static_cast(action)); - obj.set(schema.idx_url, partition_key_value); + obj.set(schema.idx_partition, partition_key_value); obj.set(schema.idx_user_identity, local_uuid); transaction.commit(); } @@ -478,11 +564,16 @@ SyncUser::State SyncUserMetadata::state() const return SyncUser::State(m_obj.get(m_schema.state_col)); } -std::string SyncUserMetadata::local_uuid() const +std::vector SyncUserMetadata::legacy_identities() const { REALM_ASSERT(m_realm); m_realm->refresh(); - return m_obj.get(m_schema.local_uuid_col); + std::vector uuids; + auto list = m_obj.get_list(m_schema.legacy_uuids_col); + for (size_t i = 0, size = list.size(); i < size; ++i) { + uuids.push_back(list.get(i)); + } + return uuids; } std::string SyncUserMetadata::refresh_token() const @@ -520,19 +611,22 @@ std::vector SyncUserMetadata::identities() const std::vector identities; for (size_t i = 0; i < linklist.size(); i++) { - auto obj_key = linklist.get(i); - auto obj = linklist.get_target_table()->get_object(obj_key); + auto obj = linklist.get_object(i); identities.push_back(user_identity_from_obj(obj)); } return identities; } -std::string SyncUserMetadata::provider_type() const +SyncUserProfile SyncUserMetadata::profile() const { REALM_ASSERT(m_realm); m_realm->refresh(); - return m_obj.get(m_schema.provider_type_col); + StringData result = m_obj.get(m_schema.profile_dump_col); + if (result.size() == 0) { + return SyncUserProfile(); + } + return SyncUserProfile(static_cast(bson::parse(std::string_view(result)))); } void SyncUserMetadata::set_refresh_token(const std::string& refresh_token) @@ -586,17 +680,9 @@ void SyncUserMetadata::set_identities(std::vector identities) link_list.clear(); for (auto& ident : identities) { - ObjKey obj_key = identities_table->where() - .equal(col_user_id, StringData(ident.id)) - .equal(col_provider_type, StringData(ident.provider_type)) - .find(); - if (!obj_key) { - auto obj = link_list.get_target_table()->create_object(); - obj.set(c_sync_user_id, ident.id); - obj.set(c_sync_provider_type, ident.provider_type); - obj_key = obj.get_key(); - } - link_list.add(obj_key); + auto obj = link_list.create_and_insert_linked_object(link_list.size()); + obj.set(col_user_id, ident.id); + obj.set(col_provider_type, ident.provider_type); } m_realm->commit_transaction(); @@ -624,15 +710,14 @@ void SyncUserMetadata::set_device_id(const std::string& device_id) m_realm->commit_transaction(); } -SyncUserProfile SyncUserMetadata::profile() const +void SyncUserMetadata::set_legacy_identities(const std::vector& uuids) { - REALM_ASSERT(m_realm); - m_realm->refresh(); - StringData result = m_obj.get(m_schema.profile_dump_col); - if (result.size() == 0) { - return SyncUserProfile(); - } - return SyncUserProfile(static_cast(bson::parse(std::string_view(result)))); + m_realm->begin_transaction(); + auto list = m_obj.get_list(m_schema.legacy_uuids_col); + list.clear(); + for (auto& uuid : uuids) + list.add(uuid); + m_realm->commit_transaction(); } void SyncUserMetadata::set_user_profile(const SyncUserProfile& profile) @@ -671,16 +756,6 @@ void SyncUserMetadata::add_realm_file_path(const std::string& path) m_realm->commit_transaction(); } -void SyncUserMetadata::mark_for_removal() -{ - if (m_invalid) - return; - - m_realm->begin_transaction(); - m_obj.set(m_schema.marked_for_removal_col, true); - m_realm->commit_transaction(); -} - void SyncUserMetadata::remove() { m_invalid = true; @@ -728,11 +803,11 @@ SyncFileActionMetadata::Action SyncFileActionMetadata::action() const return static_cast(m_obj.get(m_schema.idx_action)); } -std::string SyncFileActionMetadata::url() const +std::string SyncFileActionMetadata::partition() const { REALM_ASSERT(m_realm); m_realm->refresh(); - return m_obj.get(m_schema.idx_url); + return m_obj.get(m_schema.idx_partition); } void SyncFileActionMetadata::remove() @@ -751,5 +826,3 @@ void SyncFileActionMetadata::set_action(Action new_action) m_obj.set(m_schema.idx_action, static_cast(new_action)); m_realm->commit_transaction(); } - -} // namespace realm diff --git a/src/realm/object-store/sync/impl/sync_metadata.hpp b/src/realm/object-store/sync/impl/sync_metadata.hpp index bada9c22c3a..2ab0d84b3cf 100644 --- a/src/realm/object-store/sync/impl/sync_metadata.hpp +++ b/src/realm/object-store/sync/impl/sync_metadata.hpp @@ -52,16 +52,14 @@ class SyncAppMetadata { class SyncUserMetadata { public: struct Schema { - // The ROS identity of the user. This, plus the auth server URL, uniquely identifies a user. + // The server-supplied user_id for the user. Unique per App. ColKey identity_col; - // A locally issued UUID for the user. This is used to generate the on-disk user directory. - ColKey local_uuid_col; - // Whether or not this user has been marked for removal. - ColKey marked_for_removal_col; + // Locally generated UUIDs for the user. These are tracked to be able + // to open pre-existing Realm files, but are no longer generated or + // used for anything else. + ColKey legacy_uuids_col; // The cached refresh token for this user. ColKey refresh_token_col; - // The URL of the authentication server this user resides upon. - ColKey provider_type_col; // The cached access token for this user. ColKey access_token_col; // The identities for this user. @@ -79,8 +77,9 @@ class SyncUserMetadata { // Cannot be set after creation. std::string identity() const; - // Cannot be set after creation. - std::string local_uuid() const; + std::vector legacy_identities() const; + // for testing purposes only + void set_legacy_identities(const std::vector&); std::vector identities() const; void set_identities(std::vector); @@ -107,13 +106,6 @@ class SyncUserMetadata { SyncUser::State state() const; - // Cannot be set after creation. - std::string provider_type() const; - - // Mark the user as "ready for removal". Since Realm files cannot be safely deleted after being opened, the actual - // deletion of a user must be deferred until the next time the host application is launched. - void mark_for_removal(); - void remove(); bool is_valid() const @@ -141,8 +133,8 @@ class SyncFileActionMetadata { ColKey idx_new_name; // An enum describing the action to take. ColKey idx_action; - // The full remote URL of the Realm on the ROS. - ColKey idx_url; + // The partition key of the Realm. + ColKey idx_partition; // The local UUID of the user to whom the file action applies (despite the internal column name). ColKey idx_user_identity; }; @@ -167,7 +159,7 @@ class SyncFileActionMetadata { std::string user_local_uuid() const; Action action() const; - std::string url() const; + std::string partition() const; void remove(); void set_action(Action new_action); @@ -229,7 +221,6 @@ class SyncMetadataManager { // Retrieve or create user metadata. // Note: if `make_is_absent` is true and the user has been marked for deletion, it will be unmarked. util::Optional get_or_make_user_metadata(const std::string& identity, - const std::string& provider_type, bool make_if_absent = true) const; // Retrieve file action metadata. diff --git a/src/realm/object-store/sync/sync_manager.cpp b/src/realm/object-store/sync/sync_manager.cpp index 39bcae9e369..9e73196d3a6 100644 --- a/src/realm/object-store/sync/sync_manager.cpp +++ b/src/realm/object-store/sync/sync_manager.cpp @@ -50,7 +50,6 @@ void SyncManager::configure(std::shared_ptr app, const std::string& sy std::string identity; std::string refresh_token; std::string access_token; - std::string provider_type; std::vector identities; SyncUser::State state; std::string device_id; @@ -117,8 +116,8 @@ void SyncManager::configure(std::shared_ptr app, const std::string& sy auto device_id = user_data.device_id(); if (!refresh_token.empty() && !access_token.empty()) { users_to_add.push_back(UserCreationData{user_data.identity(), std::move(refresh_token), - std::move(access_token), user_data.provider_type(), - user_data.identities(), user_data.state(), device_id}); + std::move(access_token), user_data.identities(), + user_data.state(), device_id}); } } @@ -147,10 +146,8 @@ void SyncManager::configure(std::shared_ptr app, const std::string& sy util::CheckedLockGuard lock(m_user_mutex); for (auto& user_data : users_to_add) { auto& identity = user_data.identity; - auto& provider_type = user_data.provider_type; - auto user = - std::make_shared(user_data.refresh_token, identity, provider_type, user_data.access_token, - user_data.state, user_data.device_id, this); + auto user = std::make_shared(user_data.refresh_token, identity, user_data.access_token, + user_data.state, user_data.device_id, this); user->update_identities(user_data.identities); m_users.emplace_back(std::move(user)); } @@ -355,19 +352,16 @@ bool SyncManager::perform_metadata_update(util::FunctionRef SyncManager::get_user(const std::string& user_id, std::string refresh_token, - std::string access_token, const std::string provider_type, - std::string device_id) + std::string access_token, std::string device_id) { util::CheckedLockGuard lock(m_user_mutex); - auto it = std::find_if(m_users.begin(), m_users.end(), [user_id, provider_type](const auto& user) { - return user->identity() == user_id && user->provider_type() == provider_type && - user->state() != SyncUser::State::Removed; + auto it = std::find_if(m_users.begin(), m_users.end(), [&](const auto& user) { + return user->identity() == user_id && user->state() != SyncUser::State::Removed; }); if (it == m_users.end()) { // No existing user. - auto new_user = - std::make_shared(std::move(refresh_token), user_id, provider_type, std::move(access_token), - SyncUser::State::LoggedIn, device_id, this); + auto new_user = std::make_shared(std::move(refresh_token), user_id, std::move(access_token), + SyncUser::State::LoggedIn, device_id, this); m_users.emplace(m_users.begin(), new_user); { util::CheckedLockGuard lock(m_file_system_mutex); @@ -475,23 +469,12 @@ void SyncManager::remove_user(const std::string& user_id) if (!user) return; user->set_state(SyncUser::State::Removed); - - util::CheckedLockGuard fs_lock(m_file_system_mutex); - if (!m_metadata_manager) - return; - - for (size_t i = 0; i < m_metadata_manager->all_unmarked_users().size(); i++) { - auto metadata = m_metadata_manager->all_unmarked_users().get(i); - if (user->identity() == metadata.identity()) { - metadata.mark_for_removal(); - } - } } void SyncManager::delete_user(const std::string& user_id) { util::CheckedLockGuard lock(m_user_mutex); - // Avoid itterating over m_users twice by not calling `get_user_for_identity`. + // Avoid iterating over m_users twice by not calling `get_user_for_identity`. auto it = std::find_if(m_users.begin(), m_users.end(), [&user_id](auto& user) { return user->identity() == user_id; }); @@ -611,12 +594,12 @@ std::string SyncManager::path_for_realm(const SyncConfig& config, util::Optional } return string_from_partition(config.partition_value); }(); - path = m_file_manager->realm_file_path(user->identity(), user->local_identity(), file_name, + path = m_file_manager->realm_file_path(user->identity(), user->legacy_identities(), file_name, config.partition_value); } // Report the use of a Realm for this user, so the metadata can track it for clean up. perform_metadata_update([&](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(user->identity(), user->provider_type()); + auto metadata = manager.get_or_make_user_metadata(user->identity()); metadata->add_realm_file_path(path); }); return path; diff --git a/src/realm/object-store/sync/sync_manager.hpp b/src/realm/object-store/sync/sync_manager.hpp index 1f8a29f3f05..8d38ecfc2ff 100644 --- a/src/realm/object-store/sync/sync_manager.hpp +++ b/src/realm/object-store/sync/sync_manager.hpp @@ -181,8 +181,7 @@ class SyncManager : public std::enable_shared_from_this { // Get a sync user for a given identity, or create one if none exists yet, and set its token. // If a logged-out user exists, it will marked as logged back in. std::shared_ptr get_user(const std::string& id, std::string refresh_token, std::string access_token, - const std::string provider_type, std::string device_id) - REQUIRES(!m_user_mutex, !m_file_system_mutex); + std::string device_id) REQUIRES(!m_user_mutex, !m_file_system_mutex); // Get an existing user for a given identifier, if one exists and is logged in. std::shared_ptr get_existing_logged_in_user(const std::string& user_id) const REQUIRES(!m_user_mutex); diff --git a/src/realm/object-store/sync/sync_user.cpp b/src/realm/object-store/sync/sync_user.cpp index 515b8341aa7..2fe79c3c275 100644 --- a/src/realm/object-store/sync/sync_user.cpp +++ b/src/realm/object-store/sync/sync_user.cpp @@ -84,17 +84,15 @@ SyncUserIdentity::SyncUserIdentity(const std::string& id, const std::string& pro SyncUserContextFactory SyncUser::s_binding_context_factory; std::mutex SyncUser::s_binding_context_factory_mutex; -SyncUser::SyncUser(std::string refresh_token, const std::string identity, const std::string provider_type, - std::string access_token, SyncUser::State state, const std::string device_id, - SyncManager* sync_manager) - : m_provider_type(provider_type) +SyncUser::SyncUser(std::string refresh_token, std::string identity, std::string access_token, SyncUser::State state, + std::string device_id, SyncManager* sync_manager) + : m_state(state) , m_identity(std::move(identity)) , m_refresh_token(RealmJWT(std::move(refresh_token))) , m_access_token(RealmJWT(std::move(access_token))) - , m_device_id(device_id) + , m_device_id(std::move(device_id)) , m_sync_manager(sync_manager) { - m_state.store(state); { std::lock_guard lock(s_binding_context_factory_mutex); if (s_binding_context_factory) { @@ -102,19 +100,15 @@ SyncUser::SyncUser(std::string refresh_token, const std::string identity, const } } - bool updated = m_sync_manager->perform_metadata_update([&](const auto& manager) NO_THREAD_SAFETY_ANALYSIS { - auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); + m_sync_manager->perform_metadata_update([&](const auto& manager) NO_THREAD_SAFETY_ANALYSIS { + auto metadata = manager.get_or_make_user_metadata(m_identity); metadata->set_state_and_tokens(state, m_access_token.token, m_refresh_token.token); metadata->set_device_id(m_device_id); - m_local_identity = metadata->local_uuid(); + m_legacy_identities = metadata->legacy_identities(); this->m_user_profile = metadata->profile(); }); - if (!updated) - m_local_identity = m_identity; } -SyncUser::~SyncUser() {} - std::shared_ptr SyncUser::sync_manager() const { util::CheckedLockGuard lk(m_mutex); @@ -195,8 +189,8 @@ void SyncUser::update_state_and_tokens(SyncUser::State state, const std::string& } } - m_sync_manager->perform_metadata_update([&, state = m_state.load()](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); + m_sync_manager->perform_metadata_update([&](const auto& manager) { + auto metadata = manager.get_or_make_user_metadata(m_identity); metadata->set_state_and_tokens(state, access_token, refresh_token); }); } @@ -245,7 +239,7 @@ void SyncUser::update_refresh_token(std::string&& token) } m_sync_manager->perform_metadata_update([&, raw_refresh_token = m_refresh_token.token](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); + auto metadata = manager.get_or_make_user_metadata(m_identity); metadata->set_refresh_token(raw_refresh_token); }); } @@ -280,7 +274,7 @@ void SyncUser::update_access_token(std::string&& token) } m_sync_manager->perform_metadata_update([&, raw_access_token = m_access_token.token](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); + auto metadata = manager.get_or_make_user_metadata(m_identity); metadata->set_access_token(raw_access_token); }); } @@ -312,7 +306,7 @@ void SyncUser::update_identities(std::vector identities) m_user_identities = identities; m_sync_manager->perform_metadata_update([&](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); + auto metadata = manager.get_or_make_user_metadata(m_identity); metadata->set_identities(identities); }); } @@ -324,29 +318,31 @@ void SyncUser::log_out() std::shared_ptr sync_manager_shared; { util::CheckedLockGuard lock(m_mutex); + bool is_anonymous = false; { util::CheckedLockGuard lock2(m_tokens_mutex); if (m_state != State::LoggedIn) { return; } + is_anonymous = do_is_anonymous(); m_state = State::LoggedOut; m_access_token = RealmJWT{}; m_refresh_token = RealmJWT{}; } - if (this->m_provider_type == app::IdentityProviderAnonymous) { + if (is_anonymous) { // An Anonymous user can not log back in. // Mark the user as 'dead' in the persisted metadata Realm. m_state = State::Removed; m_sync_manager->perform_metadata_update([&](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type, false); + auto metadata = manager.get_or_make_user_metadata(m_identity, false); if (metadata) metadata->remove(); }); } else { m_sync_manager->perform_metadata_update([&](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); + auto metadata = manager.get_or_make_user_metadata(m_identity); metadata->set_state_and_tokens(State::LoggedOut, "", ""); }); } @@ -375,7 +371,20 @@ bool SyncUser::is_logged_in() const bool SyncUser::do_is_logged_in() const { - return !m_access_token.token.empty() && !m_refresh_token.token.empty() && state() == State::LoggedIn; + return !m_access_token.token.empty() && !m_refresh_token.token.empty() && m_state == State::LoggedIn; +} + +bool SyncUser::is_anonymous() const +{ + util::CheckedLockGuard lock(m_mutex); + util::CheckedLockGuard lock2(m_tokens_mutex); + return do_is_anonymous(); +} + +bool SyncUser::do_is_anonymous() const +{ + return do_is_logged_in() && m_user_identities.size() == 1 && + m_user_identities[0].provider_type == app::IdentityProviderAnonymous; } void SyncUser::invalidate() @@ -407,8 +416,9 @@ bool SyncUser::has_device_id() const return !m_device_id.empty() && m_device_id != "000000000000000000000000"; } -SyncUser::State SyncUser::state() const NO_THREAD_SAFETY_ANALYSIS +SyncUser::State SyncUser::state() const { + util::CheckedLockGuard lock(m_mutex); return m_state; } @@ -419,7 +429,7 @@ void SyncUser::set_state(SyncUser::State state) REALM_ASSERT(m_sync_manager); m_sync_manager->perform_metadata_update([&](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); + auto metadata = manager.get_or_make_user_metadata(m_identity); metadata->set_state(state); }); } @@ -446,7 +456,7 @@ void SyncUser::update_user_profile(const SyncUserProfile& profile) m_user_profile = profile; m_sync_manager->perform_metadata_update([&](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); + auto metadata = manager.get_or_make_user_metadata(m_identity); metadata->set_user_profile(profile); }); } @@ -526,7 +536,8 @@ bool SyncUser::access_token_refresh_required() const { using namespace std::chrono; constexpr size_t buffer_seconds = 5; // arbitrary - util::CheckedLockGuard lock(m_tokens_mutex); + util::CheckedLockGuard lock(m_mutex); + util::CheckedLockGuard lock2(m_tokens_mutex); const auto now = duration_cast(system_clock::now().time_since_epoch()).count() + m_seconds_to_adjust_time_for_testing.load(std::memory_order_relaxed); const auto threshold = now - buffer_seconds; diff --git a/src/realm/object-store/sync/sync_user.hpp b/src/realm/object-store/sync/sync_user.hpp index f7a5216fbcb..12b0cab083b 100644 --- a/src/realm/object-store/sync/sync_user.hpp +++ b/src/realm/object-store/sync/sync_user.hpp @@ -180,10 +180,8 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba }; // Don't use this directly; use the `SyncManager` APIs. Public for use with `make_shared`. - SyncUser(std::string refresh_token, const std::string id, const std::string provider_type, - std::string access_token, SyncUser::State state, const std::string device_id, SyncManager* sync_manager); - - ~SyncUser(); + SyncUser(std::string refresh_token, std::string id, std::string access_token, SyncUser::State state, + std::string device_id, SyncManager* sync_manager); // Return a list of all sessions belonging to this user. std::vector> all_sessions() REQUIRES(!m_mutex); @@ -219,22 +217,20 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba // Log the user out and mark it as such. This will also close its associated Sessions. void log_out() REQUIRES(!m_mutex, !m_tokens_mutex); - /// Returns true id the users access_token and refresh_token are set. + /// Returns true if the users access_token and refresh_token are set. bool is_logged_in() const REQUIRES(!m_mutex, !m_tokens_mutex); + /// Returns true if the user's only identity is anonymous. + bool is_anonymous() const REQUIRES(!m_mutex, !m_tokens_mutex); + const std::string& identity() const noexcept { return m_identity; } - const std::string& provider_type() const noexcept - { - return m_provider_type; - } - - const std::string& local_identity() const noexcept + const std::vector& legacy_identities() const noexcept { - return m_local_identity; + return m_legacy_identities; } std::string access_token() const REQUIRES(!m_tokens_mutex); @@ -252,7 +248,7 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba // Custom user data embedded in the access token. util::Optional custom_data() const REQUIRES(!m_tokens_mutex); - State state() const; + State state() const REQUIRES(!m_mutex); void set_state(SyncUser::State state) REQUIRES(!m_mutex); std::shared_ptr binding_context() const @@ -276,7 +272,7 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba /// Checks the expiry on the access token against the local time and if it is invalid or expires soon, returns /// true. - bool access_token_refresh_required() const REQUIRES(!m_tokens_mutex); + bool access_token_refresh_required() const REQUIRES(!m_mutex, !m_tokens_mutex); // Optionally set a context factory. If so, must be set before any sessions are created. static void set_binding_context_factory(SyncUserContextFactory factory); @@ -311,19 +307,18 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba static SyncUserContextFactory s_binding_context_factory; static std::mutex s_binding_context_factory_mutex; - bool do_is_logged_in() const REQUIRES(m_tokens_mutex); + bool do_is_logged_in() const REQUIRES(m_tokens_mutex, m_mutex); + bool do_is_anonymous() const REQUIRES(m_tokens_mutex, m_mutex); std::vector> revive_sessions() REQUIRES(m_mutex); - std::atomic m_state GUARDED_BY(m_mutex); + State m_state GUARDED_BY(m_mutex); util::AtomicSharedPtr m_binding_context; - // A locally assigned UUID intended to provide a level of indirection for various features. - std::string m_local_identity; - - // The auth provider used to login this user. - const std::string m_provider_type; + // UUIDs which used to be used to generate local Realm file paths. Now only + // used to locate existing files. + std::vector m_legacy_identities; // Mark the user as invalid, since a fatal user-related error was encountered. void invalidate() REQUIRES(!m_mutex); diff --git a/test/object-store/CMakeLists.txt b/test/object-store/CMakeLists.txt index f249c5f011f..e07c4268caa 100644 --- a/test/object-store/CMakeLists.txt +++ b/test/object-store/CMakeLists.txt @@ -54,13 +54,15 @@ endif() if(REALM_ENABLE_SYNC) list(APPEND HEADERS + util/sync/baas_admin_api.hpp util/sync/flx_sync_harness.hpp util/sync/session_util.hpp util/sync/sync_test_utils.hpp - util/sync/baas_admin_api.hpp + util/unit_test_transport.hpp ) list(APPEND SOURCES bson.cpp + sync/app.cpp sync/client_reset.cpp sync/file.cpp sync/flx_migration.cpp @@ -74,8 +76,8 @@ if(REALM_ENABLE_SYNC) sync/session/wait_for_completion.cpp sync/sync_manager.cpp sync/user.cpp - sync/app.cpp util/sync/sync_test_utils.cpp + util/unit_test_transport.cpp ) if(APPLE) list(APPEND SOURCES audit.cpp) diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 7ad3e1be116..0d07d18db16 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -16,21 +16,18 @@ // //////////////////////////////////////////////////////////////////////////// -#include -#include -#include +#include "util/test_file.hpp" +#include "util/event_loop.hpp" #include -#include #include #include #include #include +#include #include - #include - #include #include @@ -42,21 +39,22 @@ #include #if REALM_ENABLE_SYNC +#include "util/sync/flx_sync_harness.hpp" +#include "util/sync/sync_test_utils.hpp" +#include "util/unit_test_transport.hpp" + +#include #include +#include +#include +#include +#include #include #endif #if REALM_ENABLE_AUTH_TESTS -#include -#include - -#include - -#include -#include - -#include +#include "util/sync/baas_admin_api.hpp" #endif using namespace realm; @@ -308,126 +306,6 @@ CPtr clone_cptr(const T* ptr) } \ } while (false); -#if REALM_ENABLE_AUTH_TESTS -class CApiUnitTestTransport : public app::GenericNetworkTransport { - std::string m_provider_type; - -public: - CApiUnitTestTransport(const std::string& provider_type = {}, uint64_t request_timeout = 60000) - : m_provider_type(provider_type.empty() ? "anon-user" : provider_type) - , request_timeout(request_timeout) - { - profile_0 = nlohmann::json({{"name", "profile_0_name"}, - {"first_name", "profile_0_first_name"}, - {"last_name", "profile_0_last_name"}, - {"email", "profile_0_email"}, - {"picture_url", "profile_0_picture_url"}, - {"gender", "profile_0_gender"}, - {"birthday", "profile_0_birthday"}, - {"min_age", "profile_0_min_age"}, - {"max_age", "profile_0_max_age"}}); - } - - explicit CApiUnitTestTransport(const uint64_t request_timeout) - : CApiUnitTestTransport({}, request_timeout) - { - } - - void set_provider_type(const std::string& provider_type) - { - m_provider_type = provider_type; - } - - const std::string access_token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." - "eyJleHAiOjE1ODE1MDc3OTYsImlhdCI6MTU4MTUwNTk5NiwiaXNzIjoiNWU0M2RkY2M2MzZlZTEwNmVhYTEyYmRjIiwic3RpdGNoX2Rldklk" - "IjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWUxNDk5MTNjOTBiNGFmMGViZTkzNTI3Iiwic3ViIjoi" - "NWU0M2RkY2M2MzZlZTEwNmVhYTEyYmRhIiwidHlwIjoiYWNjZXNzIn0.0q3y9KpFxEnbmRwahvjWU1v9y1T1s3r2eozu93vMc3s"; - const std::string user_id = "awelfkewjfewkefkeafj"; - const std::string identity_0_id = "eflkjf393flkj33fjf3"; - const std::string identity_1_id = "aewfjklewfwoifejjef"; - nlohmann::json profile_0; - uint64_t request_timeout; - - -private: - void handle_profile(const app::Request&, util::UniqueFunction&& completion) - { - std::string response = - nlohmann::json({{"user_id", user_id}, - {"identities", - {{{"id", identity_0_id}, {"provider_type", m_provider_type}, {"provider_id", "lol"}}, - {{"id", identity_1_id}, {"provider_type", "lol_wut"}, {"provider_id", "nah_dawg"}}}}, - {"data", profile_0}}) - .dump(); - - completion(app::Response{200, 0, {}, response}); - } - - void handle_login(const app::Request& request, util::UniqueFunction&& completion) - { - CHECK(request.method == app::HttpMethod::post); - auto item = app::AppUtils::find_header("Content-Type", request.headers); - CHECK(item); - CHECK(item->second == "application/json;charset=utf-8"); - // Verify against - CHECK(nlohmann::json::parse(request.body)["options"] == - nlohmann::json({{"device", - {{"appId", "app_id_123"}, - {"platform", util::get_library_platform()}, - {"platformVersion", "some_platform_version"}, - {"sdk", "some_sdk_name"}, - {"sdkVersion", "some_sdk_version"}, - {"cpuArch", util::get_library_cpu_arch()}, - {"deviceName", "some_device_name"}, - {"deviceVersion", "some_device_version"}, - {"frameworkName", "some_framework_name"}, - {"frameworkVersion", "some_framework_version"}, - {"coreVersion", REALM_VERSION_STRING}, - {"bundleId", "some_bundle_id"}}}})); - - CHECK(request.timeout_ms == request_timeout); - - std::string response = nlohmann::json({{"access_token", access_token}, - {"refresh_token", access_token}, - {"user_id", user_id}, - {"device_id", "Panda Bear"}}) - .dump(); - - completion(app::Response{200, 0, {}, response}); - } - - void handle_location(const app::Request&, util::UniqueFunction&& completion) - { - std::string response = nlohmann::json({{"deployment_model", "this"}, - {"hostname", "field"}, - {"ws_hostname", "shouldn't"}, - {"location", "matter"}}) - .dump(); - - completion(app::Response{200, 0, {}, response}); - } - -public: - void send_request_to_server(const app::Request& request, - util::UniqueFunction&& completion) override - { - if (request.url.find("/login") != std::string::npos) { - handle_login(request, std::move(completion)); - } - else if (request.url.find("/profile") != std::string::npos) { - handle_profile(request, std::move(completion)); - } - else if (request.url.find("/location") != std::string::npos && request.method == app::HttpMethod::get) { - handle_location(request, std::move(completion)); - } - else { - completion(app::Response{200, 0, {}, "something arbitrary"}); - } - } -}; -#endif // REALM_ENABLE_AUTH_TESTS - TEST_CASE("C API (C)", "[c_api]") { TestFile file; CHECK(realm_c_api_tests(file.path.c_str()) == 0); @@ -644,11 +522,24 @@ TEST_CASE("C API (non-database)", "[c_api]") { } } -#if REALM_ENABLE_AUTH_TESTS +#if REALM_ENABLE_SYNC SECTION("realm_app_config_t") { const uint64_t request_timeout = 2500; - std::shared_ptr transport = - std::make_shared(request_timeout); + auto transport = std::make_shared(request_timeout); + transport->set_expected_options({{"device", + {{"appId", "app_id_123"}, + {"platform", util::get_library_platform()}, + {"platformVersion", "some_platform_version"}, + {"sdk", "some_sdk_name"}, + {"sdkVersion", "some_sdk_version"}, + {"cpuArch", util::get_library_cpu_arch()}, + {"deviceName", "some_device_name"}, + {"deviceVersion", "some_device_version"}, + {"frameworkName", "some_framework_name"}, + {"frameworkVersion", "some_framework_version"}, + {"coreVersion", REALM_VERSION_STRING}, + {"bundleId", "some_bundle_id"}}}}); + auto http_transport = realm_http_transport(transport); auto app_config = cptr(realm_app_config_new("app_id_123", &http_transport)); CHECK(app_config.get() != nullptr); @@ -693,8 +584,7 @@ TEST_CASE("C API (non-database)", "[c_api]") { CHECK(!error); }); } - -#endif // REALM_ENABLE_AUTH_TESTS +#endif // REALM_ENABLE_SYNC } namespace { @@ -5245,7 +5135,7 @@ TEST_CASE("C API - binding callback thread observer", "[sync][c_api]") { } #endif -#ifdef REALM_ENABLE_AUTH_TESTS +#if REALM_ENABLE_AUTH_TESTS std::atomic_bool baas_client_stop{false}; std::atomic error_handler_counter{0}; @@ -5517,7 +5407,6 @@ TEST_CASE("C API app: link_user integration w/c_api transport", "[sync][app][c_a realm_app_credentials anonymous(app::AppCredentials::anonymous()); realm_app_log_in_with_credentials(&app, &anonymous, realm_app_user1, &sync_user_1, nullptr); - CHECK(realm_user_get_auth_provider(sync_user_1) == RLM_AUTH_PROVIDER_ANONYMOUS); realm_app_remove_user(&app, sync_user_1, realm_app_void_completion, nullptr, nullptr); auto state = realm_user_get_state(sync_user_1); CHECK(state == RLM_USER_STATE_REMOVED); @@ -5532,7 +5421,6 @@ TEST_CASE("C API app: link_user integration w/c_api transport", "[sync][app][c_a realm_app_credentials anonymous(app::AppCredentials::anonymous()); realm_app_log_in_with_credentials(&app, &anonymous, realm_app_user1, &sync_user, nullptr); - CHECK(realm_user_get_auth_provider(sync_user) == RLM_AUTH_PROVIDER_ANONYMOUS); realm_app_delete_user(&app, sync_user, realm_app_void_completion, nullptr, nullptr); auto state = realm_user_get_state(sync_user); CHECK(state == RLM_USER_STATE_REMOVED); @@ -5550,8 +5438,6 @@ TEST_CASE("C API app: link_user integration w/c_api transport", "[sync][app][c_a realm_app_credentials anonymous(app::AppCredentials::anonymous()); realm_app_log_in_with_credentials(&app, &anonymous, realm_app_user1, &sync_user, nullptr); - CHECK(realm_user_get_auth_provider(sync_user) == RLM_AUTH_PROVIDER_ANONYMOUS); - realm_app_credentials email_creds(creds); realm_app_link_user(&app, sync_user, &email_creds, realm_app_user2, &processed, nullptr); @@ -5567,7 +5453,6 @@ TEST_CASE("C API app: link_user integration w/c_api transport", "[sync][app][c_a realm_app_void_completion, nullptr, nullptr); realm_app_credentials anonymous(app::AppCredentials::anonymous()); realm_app_log_in_with_credentials(&app, &anonymous, realm_app_user1, &sync_user_1, nullptr); - CHECK(realm_user_get_auth_provider(sync_user_1) == RLM_AUTH_PROVIDER_ANONYMOUS); auto current_user = realm_app_get_current_user(&app); CHECK(realm_equals(sync_user_1, current_user)); realm_release(current_user); @@ -5599,7 +5484,6 @@ TEST_CASE("C API app: link_user integration w/c_api transport", "[sync][app][c_a realm_app_void_completion, nullptr, nullptr); realm_app_credentials anonymous(app::AppCredentials::anonymous()); realm_app_log_in_with_credentials(&app, &anonymous, realm_app_user1, &sync_user_1, nullptr); - CHECK(realm_user_get_auth_provider(sync_user_1) == RLM_AUTH_PROVIDER_ANONYMOUS); auto callback = [](realm_userdata_t, realm_app_user_apikey_t[], size_t count, realm_app_error_t* error) { CHECK(error); CHECK(count == 0); diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index a97761be4c2..95c849ba8f6 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -16,50 +16,40 @@ // //////////////////////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include +#include "collection_fixtures.hpp" +#include "util/sync/baas_admin_api.hpp" +#include "util/sync/sync_test_utils.hpp" +#include "util/unit_test_transport.hpp" #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 #include -#include using namespace realm; using namespace realm::app; @@ -72,6 +62,9 @@ using namespace std::literals::string_literals; namespace { std::shared_ptr log_in(std::shared_ptr app, AppCredentials credentials = AppCredentials::anonymous()) { + if (auto transport = dynamic_cast(app->config().transport.get())) { + transport->set_provider_type(credentials.provider_as_string()); + } std::shared_ptr user; app->log_in_with_credentials(credentials, [&](std::shared_ptr user_arg, Optional error) { REQUIRE_FALSE(error); @@ -827,40 +820,60 @@ TEST_CASE("app: auth providers function integration", "[sync][app][user][baas]") bson::BsonDocument function_params{{"realmCustomAuthFuncUserId", "123456"}}; auto credentials = AppCredentials::function(function_params); auto user = log_in(app, credentials); - REQUIRE(user->provider_type() == IdentityProviderFunction); + REQUIRE(user->identities()[0].provider_type == IdentityProviderFunction); } } // MARK: - Link User Tests -TEST_CASE("app: link_user integration", "[sync][app][user][baas]") { +TEST_CASE("app: Linking user identities", "[sync][app][user][baas]") { TestAppSession session; auto app = session.app(); + auto user = log_in(app); - SECTION("link_user integration") { - AutoVerifiedEmailCredentials creds; - bool processed = false; - std::shared_ptr sync_user; + AutoVerifiedEmailCredentials creds; + app->provider_client().register_email(creds.email, creds.password, + [&](Optional error) { + REQUIRE_FALSE(error); + }); - app->provider_client().register_email( - creds.email, creds.password, [&](Optional error) { - CAPTURE(creds.email); - CAPTURE(creds.password); - REQUIRE_FALSE(error); // first registration success - }); + SECTION("anonymous users are reused before they are linked to an identity") { + REQUIRE(user == log_in(app)); + } - sync_user = log_in(app); - CHECK(sync_user->provider_type() == IdentityProviderAnonymous); + SECTION("linking a user adds that identity to the user") { + REQUIRE(user->identities().size() == 1); + CHECK(user->identities()[0].provider_type == IdentityProviderAnonymous); - app->link_user(sync_user, creds, [&](std::shared_ptr user, Optional error) { + app->link_user(user, creds, [&](std::shared_ptr user2, Optional error) { REQUIRE_FALSE(error); - REQUIRE(user); - CHECK(user->identity() == sync_user->identity()); - CHECK(user->identities().size() == 2); - processed = true; + REQUIRE(user == user2); + REQUIRE(user->identities().size() == 2); + CHECK(user->identities()[0].provider_type == IdentityProviderAnonymous); + CHECK(user->identities()[1].provider_type == IdentityProviderUsernamePassword); }); + } - CHECK(processed); + SECTION("linking an identity makes the user no longer returned by anonymous logins") { + app->link_user(user, creds, [&](std::shared_ptr, Optional error) { + REQUIRE_FALSE(error); + }); + auto user2 = log_in(app); + REQUIRE(user != user2); + } + + SECTION("existing users are reused when logging in via linked identities") { + app->link_user(user, creds, [](std::shared_ptr, Optional error) { + REQUIRE_FALSE(error); + }); + app->log_out([](auto error) { + REQUIRE_FALSE(error); + }); + REQUIRE(user->state() == SyncUser::State::LoggedOut); + // Should give us the same user instance despite logging in with a + // different identity + REQUIRE(user == log_in(app, creds)); + REQUIRE(user->state() == SyncUser::State::LoggedIn); } } @@ -3801,7 +3814,6 @@ TEST_CASE("app: custom error handling", "[sync][app][custom errors]") { } } - static const std::string profile_0_name = "Ursus americanus Ursus boeckhi"; static const std::string profile_0_first_name = "Ursus americanus"; static const std::string profile_0_last_name = "Ursus boeckhi"; @@ -3832,208 +3844,13 @@ static nlohmann::json user_profile_json(std::string user_id = random_string(15), { return {{"user_id", user_id}, {"identities", - {{{"id", identity_0_id}, {"provider_type", provider_type}, {"provider_id", "lol"}}, - {{"id", identity_1_id}, {"provider_type", "lol_wut"}, {"provider_id", "nah_dawg"}}}}, + {{{"id", identity_0_id}, {"provider_type", provider_type}}, + {{"id", identity_1_id}, {"provider_type", "lol_wut"}}}}, {"data", profile_0}}; } // MARK: - Unit Tests -class UnitTestTransport : public GenericNetworkTransport { - std::string m_provider_type; - -public: - UnitTestTransport(const std::string& provider_type = "anon-user") - : m_provider_type(provider_type) - { - } - - static std::string access_token; - - static const std::string api_key; - static const std::string api_key_id; - static const std::string api_key_name; - static const std::string auth_route; - static const std::string user_id; - static const std::string identity_0_id; - static const std::string identity_1_id; - - void set_provider_type(const std::string& provider_type) - { - m_provider_type = provider_type; - } - -private: - void handle_profile(const Request& request, util::UniqueFunction&& completion) - { - CHECK(request.method == HttpMethod::get); - auto content_type = AppUtils::find_header("Content-Type", request.headers); - CHECK(content_type); - CHECK(content_type->second == "application/json;charset=utf-8"); - auto authorization = AppUtils::find_header("Authorization", request.headers); - CHECK(authorization); - CHECK(authorization->second == "Bearer " + access_token); - CHECK(request.body.empty()); - CHECK(request.timeout_ms == 60000); - - std::string response = - nlohmann::json({{"user_id", user_id}, - {"identities", - {{{"id", identity_0_id}, {"provider_type", m_provider_type}, {"provider_id", "lol"}}, - {{"id", identity_1_id}, {"provider_type", "lol_wut"}, {"provider_id", "nah_dawg"}}}}, - {"data", profile_0}}) - .dump(); - - completion(Response{200, 0, {}, response}); - } - - void handle_login(const Request& request, util::UniqueFunction&& completion) - { - CHECK(request.method == HttpMethod::post); - auto item = AppUtils::find_header("Content-Type", request.headers); - CHECK(item); - CHECK(item->second == "application/json;charset=utf-8"); - CHECK(nlohmann::json::parse(request.body)["options"] == - nlohmann::json({{"device", - {{"appId", "app_id"}, - {"platform", util::get_library_platform()}, - {"platformVersion", "Object Store Test Platform Version"}, - {"sdk", "SDK Name"}, - {"sdkVersion", "SDK Version"}, - {"cpuArch", util::get_library_cpu_arch()}, - {"deviceName", "Device Name"}, - {"deviceVersion", "Device Version"}, - {"frameworkName", "Framework Name"}, - {"frameworkVersion", "Framework Version"}, - {"coreVersion", REALM_VERSION_STRING}, - {"bundleId", "Bundle Id"}}}})); - - CHECK(request.timeout_ms == 60000); - - std::string response = nlohmann::json({{"access_token", access_token}, - {"refresh_token", access_token}, - {"user_id", random_string(15)}, - {"device_id", "Panda Bear"}}) - .dump(); - - completion(Response{200, 0, {}, response}); - } - - void handle_location(const Request& request, util::UniqueFunction&& completion) - { - CHECK(request.method == HttpMethod::get); - CHECK(request.timeout_ms == 60000); - - std::string response = nlohmann::json({{"deployment_model", "this"}, - {"hostname", "field"}, - {"ws_hostname", "shouldn't"}, - {"location", "matter"}}) - .dump(); - - completion(Response{200, 0, {}, response}); - } - - void handle_create_api_key(const Request& request, util::UniqueFunction&& completion) - { - CHECK(request.method == HttpMethod::post); - auto item = AppUtils::find_header("Content-Type", request.headers); - CHECK(item); - CHECK(item->second == "application/json;charset=utf-8"); - CHECK(nlohmann::json::parse(request.body) == nlohmann::json({{"name", api_key_name}})); - CHECK(request.timeout_ms == 60000); - - std::string response = - nlohmann::json({{"_id", api_key_id}, {"key", api_key}, {"name", api_key_name}, {"disabled", false}}) - .dump(); - - completion(Response{200, 0, {}, response}); - } - - void handle_fetch_api_key(const Request& request, util::UniqueFunction&& completion) - { - CHECK(request.method == HttpMethod::get); - auto item = AppUtils::find_header("Content-Type", request.headers); - CHECK(item); - CHECK(item->second == "application/json;charset=utf-8"); - - CHECK(request.body == ""); - CHECK(request.timeout_ms == 60000); - - std::string response = - nlohmann::json({{"_id", api_key_id}, {"name", api_key_name}, {"disabled", false}}).dump(); - - completion(Response{200, 0, {}, response}); - } - - void handle_fetch_api_keys(const Request& request, util::UniqueFunction&& completion) - { - CHECK(request.method == HttpMethod::get); - auto item = AppUtils::find_header("Content-Type", request.headers); - CHECK(item); - CHECK(item->second == "application/json;charset=utf-8"); - - CHECK(request.body == ""); - CHECK(request.timeout_ms == 60000); - - auto elements = std::vector(); - for (int i = 0; i < 2; i++) { - elements.push_back({{"_id", api_key_id}, {"name", api_key_name}, {"disabled", false}}); - } - - completion(Response{200, 0, {}, nlohmann::json(elements).dump()}); - } - - void handle_token_refresh(const Request& request, util::UniqueFunction&& completion) - { - CHECK(request.method == HttpMethod::post); - auto item = AppUtils::find_header("Content-Type", request.headers); - CHECK(item); - CHECK(item->second == "application/json;charset=utf-8"); - - CHECK(request.body == ""); - CHECK(request.timeout_ms == 60000); - - auto elements = std::vector(); - nlohmann::json json{{"access_token", access_token}}; - - completion(Response{200, 0, {}, json.dump()}); - } - -public: - void send_request_to_server(const Request& request, - util::UniqueFunction&& completion) override - { - if (request.url.find("/login") != std::string::npos) { - handle_login(request, std::move(completion)); - } - else if (request.url.find("/profile") != std::string::npos) { - handle_profile(request, std::move(completion)); - } - else if (request.url.find("/session") != std::string::npos && request.method != HttpMethod::post) { - completion(Response{200, 0, {}, ""}); - } - else if (request.url.find("/api_keys") != std::string::npos && request.method == HttpMethod::post) { - handle_create_api_key(request, std::move(completion)); - } - else if (request.url.find(util::format("/api_keys/%1", api_key_id)) != std::string::npos && - request.method == HttpMethod::get) { - handle_fetch_api_key(request, std::move(completion)); - } - else if (request.url.find("/api_keys") != std::string::npos && request.method == HttpMethod::get) { - handle_fetch_api_keys(request, std::move(completion)); - } - else if (request.url.find("/session") != std::string::npos && request.method == HttpMethod::post) { - handle_token_refresh(request, std::move(completion)); - } - else if (request.url.find("/location") != std::string::npos && request.method == HttpMethod::get) { - handle_location(request, std::move(completion)); - } - else { - completion(Response{200, 0, {}, "something arbitrary"}); - } - } -}; - static TestSyncManager::Config get_config() { return get_config(instance_of); @@ -4051,19 +3868,9 @@ static const std::string good_access_token2 = "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWUxNDk5MTNjOTBiNGFmMGViZTkzNTI3Iiwic3ViIjoiNWU2YmJi" "YzBhNmI3ZGZkM2UyNTA0OGIzIiwidHlwIjoiYWNjZXNzIn0.eSX4QMjIOLbdOYOPzQrD_racwLUk1HGFgxtx2a34k80"; -std::string UnitTestTransport::access_token = good_access_token; - static const std::string bad_access_token = "lolwut"; static const std::string dummy_device_id = "123400000000000000000000"; -const std::string UnitTestTransport::api_key = "lVRPQVYBJSIbGos2ZZn0mGaIq1SIOsGaZ5lrcp8bxlR5jg4OGuGwQq1GkektNQ3i"; -const std::string UnitTestTransport::api_key_id = "5e5e6f0abe4ae2a2c2c2d329"; -const std::string UnitTestTransport::api_key_name = "some_api_key_name"; -const std::string UnitTestTransport::auth_route = "https://mongodb.com/unittests"; -const std::string UnitTestTransport::user_id = "Ailuropoda melanoleuca"; -const std::string UnitTestTransport::identity_0_id = "Ursus arctos isabellinus"; -const std::string UnitTestTransport::identity_1_id = "Ursus arctos horribilis"; - TEST_CASE("subscribable unit tests", "[sync][app]") { struct Foo : public Subscribable { void event() @@ -4135,6 +3942,7 @@ TEST_CASE("subscribable unit tests", "[sync][app]") { TEST_CASE("app: login_with_credentials unit_tests", "[sync][app][user]") { auto config = get_config(); + static_cast(config.transport.get())->set_profile(profile_0); SECTION("login_anonymous good") { UnitTestTransport::access_token = good_access_token; @@ -4146,9 +3954,8 @@ TEST_CASE("app: login_with_credentials unit_tests", "[sync][app][user]") { auto user = log_in(app); - CHECK(user->identities().size() == 2); + REQUIRE(user->identities().size() == 1); CHECK(user->identities()[0].id == UnitTestTransport::identity_0_id); - CHECK(user->identities()[1].id == UnitTestTransport::identity_1_id); SyncUserProfile user_profile = user->user_profile(); CHECK(user_profile.name() == profile_0_name); @@ -4168,9 +3975,8 @@ TEST_CASE("app: login_with_credentials unit_tests", "[sync][app][user]") { auto app = tsm.app(); REQUIRE(app->all_users().size() == 1); auto user = app->all_users()[0]; - CHECK(user->identities().size() == 2); + REQUIRE(user->identities().size() == 1); CHECK(user->identities()[0].id == UnitTestTransport::identity_0_id); - CHECK(user->identities()[1].id == UnitTestTransport::identity_1_id); SyncUserProfile user_profile = user->user_profile(); CHECK(user_profile.name() == profile_0_name); @@ -4226,8 +4032,8 @@ TEST_CASE("app: UserAPIKeyProviderClient unit_tests", "[sync][app][user][api key auto app = sync_manager.app(); auto client = app->provider_client(); - std::shared_ptr logged_in_user = app->sync_manager()->get_user( - UnitTestTransport::user_id, good_access_token, good_access_token, "anon-user", dummy_device_id); + std::shared_ptr logged_in_user = + app->sync_manager()->get_user("userid", good_access_token, good_access_token, dummy_device_id); bool processed = false; ObjectId obj_id(UnitTestTransport::api_key_id.c_str()); @@ -4310,11 +4116,11 @@ TEST_CASE("app: user_semantics", "[sync][app][user]") { CHECK(app->all_users()[0]->state() == SyncUser::State::LoggedIn); CHECK(app->all_users()[1]->state() == SyncUser::State::LoggedIn); CHECK(app->current_user()->identity() == user2->identity()); - CHECK(user1->identity() != user2->identity()); + CHECK(user1 != user2); - // shuold reuse existing session + // should reuse existing session const auto user3 = login_user_anonymous(); - CHECK(user3->identity() == user1->identity()); + CHECK(user3 == user1); auto user_events_processed = 0; auto _ = user3->subscribe([&user_events_processed](auto&) { @@ -4638,7 +4444,8 @@ TEST_CASE("app: link_user", "[sync][app][user]") { auto email_pass_credentials = AppCredentials::username_password(email, password); auto sync_user = log_in(app, email_pass_credentials); - CHECK(sync_user->provider_type() == IdentityProviderUsernamePassword); + REQUIRE(sync_user->identities().size() == 2); + CHECK(sync_user->identities()[0].provider_type == IdentityProviderUsernamePassword); SECTION("successful link") { bool processed = false; @@ -4751,8 +4558,7 @@ TEST_CASE("app: refresh access token unit tests", "[sync][app][user][token]") { if (app->sync_manager()->get_current_user()) { return; } - app->sync_manager()->get_user("a_user_id", good_access_token, good_access_token, "anon-user", - dummy_device_id); + app->sync_manager()->get_user("a_user_id", good_access_token, good_access_token, dummy_device_id); }; SECTION("refresh custom data happy path") { @@ -5457,8 +5263,7 @@ TEST_CASE("app: user logs out while profile is fetched", "[sync][app][user]") { TestSyncManager sync_manager(get_config(transporter)); auto app = sync_manager.app(); - logged_in_user = app->sync_manager()->get_user(UnitTestTransport::user_id, good_access_token, good_access_token, - "anon-user", dummy_device_id); + logged_in_user = app->sync_manager()->get_user("userid", good_access_token, good_access_token, dummy_device_id); auto custom_credentials = AppCredentials::facebook("a_token"); auto [cur_user_promise, cur_user_future] = util::make_promise_future>(); diff --git a/test/object-store/sync/file.cpp b/test/object-store/sync/file.cpp index 3401bc6689e..4d9cf0b1fd5 100644 --- a/test/object-store/sync/file.cpp +++ b/test/object-store/sync/file.cpp @@ -135,7 +135,8 @@ TEST_CASE("sync_file: SyncFileManager APIs", "[sync][file]") { TestSyncManager tsm; const std::string identity = "abcdefghi"; - const std::string local_identity = "123456789"; + const std::vector legacy_identities = {"legacy1", "legacy2"}; + const auto& local_identity = legacy_identities[0]; const std::string app_id = "test_app_id*$#@!%1"; const std::string partition_str = random_string(10); const std::string partition = bson::Bson(partition_str).to_string(); @@ -147,22 +148,22 @@ TEST_CASE("sync_file: SyncFileManager APIs", "[sync][file]") { SECTION("Realm path APIs") { auto relative_path = "s_" + partition_str; - ExpectedRealmPaths expected_paths(manager_base_path.string(), app_id, identity, local_identity, partition); + ExpectedRealmPaths expected_paths(manager_base_path.string(), app_id, identity, legacy_identities, partition); SECTION("getting a Realm path") { - auto actual = manager.realm_file_path(identity, local_identity, relative_path, partition); + auto actual = manager.realm_file_path(identity, legacy_identities, relative_path, partition); REQUIRE(expected_paths.current_preferred_path == actual); } SECTION("deleting a Realm for a valid user") { - manager.realm_file_path(identity, local_identity, relative_path, partition); + manager.realm_file_path(identity, legacy_identities, relative_path, partition); // Create the required files REQUIRE(create_dummy_realm(expected_paths.current_preferred_path)); REQUIRE(File::exists(expected_paths.current_preferred_path)); REQUIRE(File::exists(expected_paths.current_preferred_path + ".lock")); REQUIRE_DIR_EXISTS(expected_paths.current_preferred_path + ".management"); // Delete the Realm - REQUIRE(manager.remove_realm(identity, local_identity, relative_path, partition)); + REQUIRE(manager.remove_realm(identity, legacy_identities, relative_path, partition)); // Ensure the files don't exist anymore REQUIRE(!File::exists(expected_paths.current_preferred_path)); REQUIRE(!File::exists(expected_paths.current_preferred_path + ".lock")); @@ -170,7 +171,7 @@ TEST_CASE("sync_file: SyncFileManager APIs", "[sync][file]") { } SECTION("deleting a Realm for an invalid user") { - REQUIRE(!manager.remove_realm("invalid_user", "invalid_ident", relative_path, partition)); + REQUIRE(!manager.remove_realm("invalid_user", legacy_identities, relative_path, partition)); } SECTION("hashed path is used if it already exists") { @@ -181,7 +182,7 @@ TEST_CASE("sync_file: SyncFileManager APIs", "[sync][file]") { REQUIRE(create_dummy_realm(expected_paths.fallback_hashed_path)); REQUIRE(File::exists(expected_paths.fallback_hashed_path)); REQUIRE(!File::exists(expected_paths.current_preferred_path)); - auto actual = manager.realm_file_path(identity, local_identity, relative_path, partition); + auto actual = manager.realm_file_path(identity, legacy_identities, relative_path, partition); REQUIRE(actual == expected_paths.fallback_hashed_path); REQUIRE(File::exists(expected_paths.fallback_hashed_path)); REQUIRE(!File::exists(expected_paths.current_preferred_path)); @@ -198,7 +199,7 @@ TEST_CASE("sync_file: SyncFileManager APIs", "[sync][file]") { REQUIRE(File::exists(expected_paths.legacy_local_id_path)); REQUIRE(!File::exists(expected_paths.current_preferred_path)); - auto actual = manager.realm_file_path(identity, local_identity, relative_path, partition); + auto actual = manager.realm_file_path(identity, legacy_identities, relative_path, partition); REQUIRE(actual == expected_paths.legacy_local_id_path); REQUIRE(File::exists(expected_paths.legacy_local_id_path)); REQUIRE(!File::exists(expected_paths.current_preferred_path)); @@ -206,6 +207,28 @@ TEST_CASE("sync_file: SyncFileManager APIs", "[sync][file]") { REQUIRE(!File::exists(expected_paths.legacy_local_id_path)); } + SECTION("multiple legacy local identities are supported") { + // ExpectedRealmPaths uses the first legacy identity, so construct + // a second one with only the second identity + const std::vector legacy_identities_2 = {"legacy2"}; + const auto& local_identity_2 = legacy_identities_2[0]; + ExpectedRealmPaths expected_paths_2(manager_base_path.string(), app_id, identity, legacy_identities_2, + partition); + + util::try_make_dir(manager_path.string()); + util::try_make_dir((manager_path / local_identity_2).string()); + REQUIRE(create_dummy_realm(expected_paths_2.legacy_local_id_path)); + + // Note: intentionally not legacy_identities_2. We're passing both + // in and validating that it'll open the second one. + auto actual = manager.realm_file_path(identity, legacy_identities, relative_path, partition); + REQUIRE(actual == expected_paths_2.legacy_local_id_path); + REQUIRE(File::exists(expected_paths_2.legacy_local_id_path)); + REQUIRE(!File::exists(expected_paths_2.current_preferred_path)); + manager.remove_user_realms(identity, {expected_paths_2.legacy_local_id_path}); + REQUIRE(!File::exists(expected_paths_2.legacy_local_id_path)); + } + SECTION("legacy sync paths are detected and used") { REQUIRE(!File::exists(expected_paths.legacy_sync_path)); REQUIRE(!File::exists(expected_paths.current_preferred_path)); @@ -215,7 +238,7 @@ TEST_CASE("sync_file: SyncFileManager APIs", "[sync][file]") { REQUIRE(create_dummy_realm(expected_paths.legacy_sync_path)); REQUIRE(File::exists(expected_paths.legacy_sync_path)); REQUIRE(!File::exists(expected_paths.current_preferred_path)); - auto actual = manager.realm_file_path(identity, local_identity, relative_path, partition); + auto actual = manager.realm_file_path(identity, legacy_identities, relative_path, partition); REQUIRE(actual == expected_paths.legacy_sync_path); REQUIRE(File::exists(expected_paths.legacy_sync_path)); REQUIRE(!File::exists(expected_paths.current_preferred_path)); @@ -226,7 +249,7 @@ TEST_CASE("sync_file: SyncFileManager APIs", "[sync][file]") { SECTION("paths have a fallback hashed location if the preferred path is too long") { const std::string long_path_name = std::string(500, 'a'); REQUIRE(long_path_name.length() > 255); // linux name length limit - auto actual = manager.realm_file_path(identity, local_identity, long_path_name, partition); + auto actual = manager.realm_file_path(identity, legacy_identities, long_path_name, partition); REQUIRE(actual.length() < 500); REQUIRE(create_dummy_realm(actual)); REQUIRE(File::exists(actual)); diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index f90abbea240..bc4a402ba49 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -3735,7 +3735,7 @@ TEST_CASE("flx: bootstrap changesets are applied continuously", "[sync][flx][boo CHECK(user_commit_version == bootstrap_version + 1); } -TEST_CASE("flx: open realm + register subscription callack while bootstrapping", +TEST_CASE("flx: open realm + register subscription callback while bootstrapping", "[sync][flx][bootstrap][async open][baas]") { FLXSyncTestHarness harness("flx_bootstrap_batching"); auto foo_obj_id = ObjectId::gen(); diff --git a/test/object-store/sync/metadata.cpp b/test/object-store/sync/metadata.cpp index a868579a076..919bfdc3111 100644 --- a/test/object-store/sync/metadata.cpp +++ b/test/object-store/sync/metadata.cpp @@ -47,64 +47,56 @@ TEST_CASE("sync_metadata: user metadata", "[sync][metadata]") { }); SyncMetadataManager manager(metadata_path, false); - const std::string provider_type = "https://realm.example.org"; SECTION("can be properly constructed") { const auto identity = "testcase1a"; - auto user_metadata = manager.get_or_make_user_metadata(identity, provider_type); + auto user_metadata = manager.get_or_make_user_metadata(identity); REQUIRE(user_metadata->identity() == identity); - REQUIRE(user_metadata->provider_type() == provider_type); REQUIRE(user_metadata->access_token().empty()); } SECTION("properly reflects updating state") { const auto identity = "testcase1b"; const std::string sample_token = "this_is_a_user_token"; - auto user_metadata = manager.get_or_make_user_metadata(identity, provider_type); + auto user_metadata = manager.get_or_make_user_metadata(identity); user_metadata->set_access_token(sample_token); REQUIRE(user_metadata->identity() == identity); - REQUIRE(user_metadata->provider_type() == provider_type); REQUIRE(user_metadata->access_token() == sample_token); } SECTION("can be properly re-retrieved from the same manager") { const auto identity = "testcase1c"; const std::string sample_token = "this_is_a_user_token"; - auto first = manager.get_or_make_user_metadata(identity, provider_type); + auto first = manager.get_or_make_user_metadata(identity); first->set_access_token(sample_token); // Get a second instance of the user metadata for the same identity. - auto second = manager.get_or_make_user_metadata(identity, provider_type, false); + auto second = manager.get_or_make_user_metadata(identity, false); REQUIRE(second->identity() == identity); - REQUIRE(second->provider_type() == provider_type); REQUIRE(second->access_token() == sample_token); } SECTION("properly reflects changes across different instances") { const auto identity = "testcase1d"; const std::string sample_token_1 = "this_is_a_user_token"; - auto first = manager.get_or_make_user_metadata(identity, provider_type); - auto second = manager.get_or_make_user_metadata(identity, provider_type); + auto first = manager.get_or_make_user_metadata(identity); + auto second = manager.get_or_make_user_metadata(identity); first->set_access_token(sample_token_1); REQUIRE(first->identity() == identity); - REQUIRE(first->provider_type() == provider_type); REQUIRE(first->access_token() == sample_token_1); REQUIRE(second->identity() == identity); - REQUIRE(second->provider_type() == provider_type); REQUIRE(second->access_token() == sample_token_1); // Set the state again. const std::string sample_token_2 = "this_is_another_user_token"; second->set_access_token(sample_token_2); REQUIRE(first->identity() == identity); - REQUIRE(first->provider_type() == provider_type); REQUIRE(first->access_token() == sample_token_2); REQUIRE(second->identity() == identity); - REQUIRE(second->provider_type() == provider_type); REQUIRE(second->access_token() == sample_token_2); } SECTION("can be removed") { const auto identity = "testcase1e"; - auto user_metadata = manager.get_or_make_user_metadata(identity, provider_type); + auto user_metadata = manager.get_or_make_user_metadata(identity); REQUIRE(user_metadata->is_valid()); user_metadata->remove(); REQUIRE(!user_metadata->is_valid()); @@ -115,25 +107,24 @@ TEST_CASE("sync_metadata: user metadata", "[sync][metadata]") { SECTION("with no prior metadata for the identifier") { const auto identity = "testcase1g1"; - auto user_metadata = manager.get_or_make_user_metadata(identity, provider_type, false); + auto user_metadata = manager.get_or_make_user_metadata(identity, false); REQUIRE(!user_metadata); } SECTION("with valid prior metadata for the identifier") { const auto identity = "testcase1g2"; - auto first = manager.get_or_make_user_metadata(identity, provider_type); + auto first = manager.get_or_make_user_metadata(identity); first->set_access_token(sample_token); - auto second = manager.get_or_make_user_metadata(identity, provider_type, false); + auto second = manager.get_or_make_user_metadata(identity, false); REQUIRE(second->is_valid()); REQUIRE(second->identity() == identity); - REQUIRE(second->provider_type() == provider_type); REQUIRE(second->access_token() == sample_token); } SECTION("with invalid prior metadata for the identifier") { const auto identity = "testcase1g3"; - auto first = manager.get_or_make_user_metadata(identity, provider_type); + auto first = manager.get_or_make_user_metadata(identity); first->set_access_token(sample_token); - first->mark_for_removal(); - auto second = manager.get_or_make_user_metadata(identity, provider_type, false); + first->set_state(SyncUser::State::Removed); + auto second = manager.get_or_make_user_metadata(identity, false); REQUIRE(!second); } } @@ -150,31 +141,24 @@ TEST_CASE("sync_metadata: user metadata APIs", "[sync][metadata]") { SECTION("properly list all marked and unmarked users") { const auto identity1 = "testcase2a1"; - const auto identity2 = "testcase2a1"; // same as identity 1 - const auto identity3 = "testcase2a3"; - const std::string provider_type_1 = "https://foobar.example.org"; - const std::string provider_type_2 = "https://realm.example.org"; - const std::string provider_type_3 = "https://realm.example.org"; - auto first = manager.get_or_make_user_metadata(identity1, provider_type_1); - auto second = manager.get_or_make_user_metadata(identity2, provider_type_2); - auto third = manager.get_or_make_user_metadata(identity3, provider_type_3); + const auto identity2 = "testcase2a3"; + auto first = manager.get_or_make_user_metadata(identity1); + auto second = manager.get_or_make_user_metadata(identity1); + auto third = manager.get_or_make_user_metadata(identity2); auto unmarked_users = manager.all_unmarked_users(); - REQUIRE(unmarked_users.size() == 3); - REQUIRE(results_contains_user(unmarked_users, identity1, provider_type_1)); - REQUIRE(results_contains_user(unmarked_users, identity2, provider_type_2)); - REQUIRE(results_contains_user(unmarked_users, identity3, provider_type_3)); + REQUIRE(unmarked_users.size() == 2); + REQUIRE(results_contains_user(unmarked_users, identity1)); + REQUIRE(results_contains_user(unmarked_users, identity2)); auto marked_users = manager.all_users_marked_for_removal(); REQUIRE(marked_users.size() == 0); // Now, mark a few users for removal. - first->mark_for_removal(); - third->mark_for_removal(); + first->set_state(SyncUser::State::Removed); unmarked_users = manager.all_unmarked_users(); REQUIRE(unmarked_users.size() == 1); - REQUIRE(results_contains_user(unmarked_users, identity2, provider_type_2)); + REQUIRE(results_contains_user(unmarked_users, identity2)); marked_users = manager.all_users_marked_for_removal(); - REQUIRE(marked_users.size() == 2); - REQUIRE(results_contains_user(marked_users, identity1, provider_type_1)); - REQUIRE(results_contains_user(marked_users, identity3, provider_type_3)); + REQUIRE(marked_users.size() == 1); + REQUIRE(results_contains_user(marked_users, identity1)); } } @@ -198,7 +182,7 @@ TEST_CASE("sync_metadata: file action metadata", "[sync][metadata]") { REQUIRE(metadata.original_name() == original_name); REQUIRE(metadata.new_name() == none); REQUIRE(metadata.action() == SyncAction::BackUpThenDeleteRealm); - REQUIRE(metadata.url() == url_1); + REQUIRE(metadata.partition() == url_1); REQUIRE(metadata.user_local_uuid() == local_uuid_1); } @@ -213,7 +197,7 @@ TEST_CASE("sync_metadata: file action metadata", "[sync][metadata]") { REQUIRE(metadata_1.original_name() == original_name); REQUIRE(metadata_1.new_name() == new_name_1); REQUIRE(metadata_1.action() == SyncAction::BackUpThenDeleteRealm); - REQUIRE(metadata_1.url() == url_1); + REQUIRE(metadata_1.partition() == url_1); REQUIRE(metadata_1.user_local_uuid() == local_uuid_1); manager.make_file_action_metadata(original_name, url_2, local_uuid_2, SyncAction::DeleteRealm, new_name_2); @@ -224,7 +208,7 @@ TEST_CASE("sync_metadata: file action metadata", "[sync][metadata]") { REQUIRE(metadata_2.original_name() == original_name); REQUIRE(metadata_2.new_name() == new_name_2); REQUIRE(metadata_2.action() == SyncAction::DeleteRealm); - REQUIRE(metadata_1.url() == url_2); + REQUIRE(metadata_1.partition() == url_2); REQUIRE(metadata_1.user_local_uuid() == local_uuid_2); } } @@ -266,44 +250,31 @@ TEST_CASE("sync_metadata: results", "[sync][metadata]") { SyncMetadataManager manager(metadata_path, false); const auto identity1 = "testcase3a1"; - const auto identity2 = "testcase3a1"; // same as identity 1 - const auto identity3 = "testcase3a3"; - const std::string provider_type_1 = "https://realm.example.org"; - const std::string provider_type_2 = "https://foobar.example.org"; - const std::string provider_type_3 = "https://realm.example.org"; - + const auto identity2 = "testcase3a3"; SECTION("properly update as underlying items are added") { auto results = manager.all_unmarked_users(); REQUIRE(results.size() == 0); // Add users, one at a time. - auto first = manager.get_or_make_user_metadata(identity1, provider_type_1); + auto first = manager.get_or_make_user_metadata(identity1); REQUIRE(results.size() == 1); - REQUIRE(results_contains_user(results, identity1, provider_type_1)); - auto second = manager.get_or_make_user_metadata(identity2, provider_type_2); + REQUIRE(results_contains_user(results, identity1)); + auto second = manager.get_or_make_user_metadata(identity2); REQUIRE(results.size() == 2); - REQUIRE(results_contains_user(results, identity2, provider_type_2)); - auto third = manager.get_or_make_user_metadata(identity3, provider_type_3); - REQUIRE(results.size() == 3); - REQUIRE(results_contains_user(results, identity3, provider_type_3)); + REQUIRE(results_contains_user(results, identity2)); } SECTION("properly update as underlying items are removed") { auto results = manager.all_unmarked_users(); - auto first = manager.get_or_make_user_metadata(identity1, provider_type_1); - auto second = manager.get_or_make_user_metadata(identity2, provider_type_2); - auto third = manager.get_or_make_user_metadata(identity3, provider_type_3); - REQUIRE(results.size() == 3); - REQUIRE(results_contains_user(results, identity1, provider_type_1)); - REQUIRE(results_contains_user(results, identity2, provider_type_2)); - REQUIRE(results_contains_user(results, identity3, provider_type_3)); - // Remove users, one at a time. - third->remove(); + auto first = manager.get_or_make_user_metadata(identity1); + auto second = manager.get_or_make_user_metadata(identity2); REQUIRE(results.size() == 2); - REQUIRE(!results_contains_user(results, identity3, provider_type_3)); + REQUIRE(results_contains_user(results, identity1)); + REQUIRE(results_contains_user(results, identity2)); + // Remove users, one at a time. first->remove(); REQUIRE(results.size() == 1); - REQUIRE(!results_contains_user(results, identity1, provider_type_1)); + REQUIRE(!results_contains_user(results, identity1)); second->remove(); REQUIRE(results.size() == 0); } @@ -320,18 +291,16 @@ TEST_CASE("sync_metadata: persistence across metadata manager instances", "[sync const std::string provider_type = "any-type"; const std::string sample_token = "this_is_a_user_token"; SyncMetadataManager first_manager(metadata_path, false); - auto first = first_manager.get_or_make_user_metadata(identity, provider_type); + auto first = first_manager.get_or_make_user_metadata(identity); first->set_access_token(sample_token); REQUIRE(first->identity() == identity); - REQUIRE(first->provider_type() == provider_type); REQUIRE(first->access_token() == sample_token); REQUIRE(first->state() == SyncUser::State::LoggedIn); first->set_state(SyncUser::State::LoggedOut); SyncMetadataManager second_manager(metadata_path, false); - auto second = second_manager.get_or_make_user_metadata(identity, provider_type, false); + auto second = second_manager.get_or_make_user_metadata(identity, false); REQUIRE(second->identity() == identity); - REQUIRE(second->provider_type() == provider_type); REQUIRE(second->access_token() == sample_token); REQUIRE(second->state() == SyncUser::State::LoggedOut); } @@ -344,7 +313,6 @@ TEST_CASE("sync_metadata: encryption", "[sync][metadata]") { }); const auto identity0 = "identity0"; - const auto auth_url = "https://realm.example.org"; SECTION("prohibits opening the metadata Realm with different keys") { SECTION("different keys") { { @@ -352,10 +320,9 @@ TEST_CASE("sync_metadata: encryption", "[sync][metadata]") { std::vector key0 = make_test_encryption_key(10); SyncMetadataManager manager0(metadata_path, true, key0); - auto user_metadata0 = manager0.get_or_make_user_metadata(identity0, auth_url); + auto user_metadata0 = manager0.get_or_make_user_metadata(identity0); REQUIRE(bool(user_metadata0)); CHECK(user_metadata0->identity() == identity0); - CHECK(user_metadata0->provider_type() == auth_url); CHECK(user_metadata0->access_token().empty()); CHECK(user_metadata0->is_valid()); } @@ -364,15 +331,14 @@ TEST_CASE("sync_metadata: encryption", "[sync][metadata]") { std::vector key1 = make_test_encryption_key(11); SyncMetadataManager manager1(metadata_path, true, key1); - auto user_metadata1 = manager1.get_or_make_user_metadata(identity0, auth_url, false); + auto user_metadata1 = manager1.get_or_make_user_metadata(identity0, false); // Expect previous metadata to have been deleted CHECK_FALSE(bool(user_metadata1)); // But new metadata can still be created const auto identity1 = "identity1"; - auto user_metadata2 = manager1.get_or_make_user_metadata(identity1, auth_url); + auto user_metadata2 = manager1.get_or_make_user_metadata(identity1); CHECK(user_metadata2->identity() == identity1); - CHECK(user_metadata2->provider_type() == auth_url); CHECK(user_metadata2->access_token().empty()); CHECK(user_metadata2->is_valid()); } @@ -381,25 +347,23 @@ TEST_CASE("sync_metadata: encryption", "[sync][metadata]") { // Encrypt metadata realm at path, make metadata SyncMetadataManager manager0(metadata_path, true, make_test_encryption_key(10)); - auto user_metadata0 = manager0.get_or_make_user_metadata(identity0, auth_url); + auto user_metadata0 = manager0.get_or_make_user_metadata(identity0); REQUIRE(bool(user_metadata0)); CHECK(user_metadata0->identity() == identity0); - CHECK(user_metadata0->provider_type() == auth_url); CHECK(user_metadata0->access_token().empty()); CHECK(user_metadata0->is_valid()); } // Metadata realm is closed because only reference to the realm (user_metadata) is now out of scope // Open new metadata realm at path with different encryption configuration SyncMetadataManager manager1(metadata_path, false); - auto user_metadata1 = manager1.get_or_make_user_metadata(identity0, auth_url, false); + auto user_metadata1 = manager1.get_or_make_user_metadata(identity0, false); // Expect previous metadata to have been deleted CHECK_FALSE(bool(user_metadata1)); // But new metadata can still be created const auto identity1 = "identity1"; - auto user_metadata2 = manager1.get_or_make_user_metadata(identity1, auth_url); + auto user_metadata2 = manager1.get_or_make_user_metadata(identity1); CHECK(user_metadata2->identity() == identity1); - CHECK(user_metadata2->provider_type() == auth_url); CHECK(user_metadata2->access_token().empty()); CHECK(user_metadata2->is_valid()); } @@ -409,18 +373,16 @@ TEST_CASE("sync_metadata: encryption", "[sync][metadata]") { std::vector key = make_test_encryption_key(10); const auto identity = "testcase5a"; SyncMetadataManager manager(metadata_path, true, key); - auto user_metadata = manager.get_or_make_user_metadata(identity, auth_url); + auto user_metadata = manager.get_or_make_user_metadata(identity); REQUIRE(bool(user_metadata)); CHECK(user_metadata->identity() == identity); - CHECK(user_metadata->provider_type() == auth_url); CHECK(user_metadata->access_token().empty()); CHECK(user_metadata->is_valid()); // Reopen the metadata file with the same key. SyncMetadataManager manager_2(metadata_path, true, key); - auto user_metadata_2 = manager_2.get_or_make_user_metadata(identity, auth_url, false); + auto user_metadata_2 = manager_2.get_or_make_user_metadata(identity, false); REQUIRE(bool(user_metadata_2)); CHECK(user_metadata_2->identity() == identity); - CHECK(user_metadata_2->provider_type() == auth_url); CHECK(user_metadata_2->is_valid()); } @@ -512,7 +474,13 @@ TEST_CASE("sync metadata: can open old metadata realms", "[sync][metadata]") { // change to true to create a test file for the current schema version // this will only work on unix-like systems if ((false)) { -#if true +#if false // The code to generate the v4 and v5 Realms + { // Create a metadata Realm with a test user + SyncMetadataManager manager(metadata_path, false); + auto user_metadata = manager.get_or_make_user_metadata(identity, provider_type); + user_metadata->set_access_token(sample_token); + } +#elif false // The code to generate the v6 Realm // Code to generate the v6 metadata Realm used to test the 6 -> 7 migration { using State = SyncUser::State; @@ -568,7 +536,7 @@ TEST_CASE("sync metadata: can open old metadata realms", "[sync][metadata]") { #else { // Create a metadata Realm with a test user SyncMetadataManager manager(metadata_path, false); - auto user_metadata = manager.get_or_make_user_metadata(identity, provider_type); + auto user_metadata = manager.get_or_make_user_metadata(identity); user_metadata->set_access_token(sample_token); } #endif @@ -600,19 +568,52 @@ TEST_CASE("sync metadata: can open old metadata realms", "[sync][metadata]") { SECTION("open schema version 4") { File::copy(test_util::get_test_resource_path() + "sync-metadata-v4.realm", metadata_path); SyncMetadataManager manager(metadata_path, false); - auto user_metadata = manager.get_or_make_user_metadata(identity, provider_type); + auto user_metadata = manager.get_or_make_user_metadata(identity); REQUIRE(user_metadata->identity() == identity); - REQUIRE(user_metadata->provider_type() == provider_type); REQUIRE(user_metadata->access_token() == sample_token); } SECTION("open schema version 5") { File::copy(test_util::get_test_resource_path() + "sync-metadata-v5.realm", metadata_path); SyncMetadataManager manager(metadata_path, false); - auto user_metadata = manager.get_or_make_user_metadata(identity, provider_type); + auto user_metadata = manager.get_or_make_user_metadata(identity); REQUIRE(user_metadata->identity() == identity); - REQUIRE(user_metadata->provider_type() == provider_type); REQUIRE(user_metadata->access_token() == sample_token); } + + SECTION("open schema version 6") { + using State = SyncUser::State; + File::copy(test_util::get_test_resource_path() + "sync-metadata-v6.realm", metadata_path); + SyncMetadataManager manager(metadata_path, false); + + SyncUserIdentity id_1{"identity 1", "a"}; + SyncUserIdentity id_2{"identity 2", "b"}; + SyncUserIdentity id_shared{"shared identity", "shared"}; + const std::vector all_ids = {id_1, id_shared, id_2}; + const std::vector realm_files = {"file 1", "file 2", "file 3"}; + + auto check_user = [&](const char* user_id, State state, const std::string& access_token, + const std::string& refresh_token, const std::vector& uuids) { + auto user = manager.get_or_make_user_metadata(user_id, false); + CAPTURE(user_id); + REQUIRE(user); + CHECK(user->state() == state); + CHECK(user->access_token() == access_token); + CHECK(user->refresh_token() == refresh_token); + CHECK(user->legacy_identities() == uuids); + CHECK(user->identities() == all_ids); + CHECK(user->realm_file_paths() == realm_files); + }; + + REQUIRE_FALSE(manager.get_or_make_user_metadata("removed user", false)); + check_user("first logged in, second logged out", State::LoggedIn, access_token_1, refresh_token_1, + {"1", "2"}); + check_user("first logged in, second removed", State::LoggedIn, access_token_1, refresh_token_1, {"3", "4"}); + check_user("second logged in, first logged out", State::LoggedIn, access_token_2, refresh_token_2, + {"5", "6"}); + check_user("second logged in, first removed", State::LoggedIn, access_token_2, refresh_token_2, {"7", "8"}); + check_user("both logged in, first newer", State::LoggedIn, access_token_2, refresh_token_1, {"9", "10"}); + check_user("both logged in, second newer", State::LoggedIn, access_token_2, refresh_token_2, {"11", "12"}); + } } #endif // SWIFT_PACKAGE diff --git a/test/object-store/sync/session/connection_change_notifications.cpp b/test/object-store/sync/session/connection_change_notifications.cpp index 4f61c0a8ae3..aabfb812494 100644 --- a/test/object-store/sync/session/connection_change_notifications.cpp +++ b/test/object-store/sync/session/connection_change_notifications.cpp @@ -41,7 +41,6 @@ using namespace realm; using namespace realm::util; -static const std::string dummy_auth_url = "https://realm.example.org"; static const std::string dummy_device_id = "123400000000000000000000"; static const std::string base_path = util::make_temp_dir() + "realm_objectstore_sync_connection_state_changes"; @@ -54,9 +53,8 @@ TEST_CASE("sync: Connection state changes", "[sync][session][connection change]" config.base_path = base_path; TestSyncManager init_sync_manager(config); auto app = init_sync_manager.app(); - auto user = - app->sync_manager()->get_user("user", ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("also_not_a_real_token"), dummy_auth_url, dummy_device_id); + auto user = app->sync_manager()->get_user("user", ENCODE_FAKE_JWT("not_a_real_token"), + ENCODE_FAKE_JWT("also_not_a_real_token"), dummy_device_id); SECTION("register connection change listener") { auto session = sync_session( diff --git a/test/object-store/sync/session/session.cpp b/test/object-store/sync/session/session.cpp index 0697d9dfd7c..7839507ff9e 100644 --- a/test/object-store/sync/session/session.cpp +++ b/test/object-store/sync/session/session.cpp @@ -41,13 +41,12 @@ using namespace realm; using namespace realm::util; -static const std::string dummy_auth_url = "https://realm.example.org"; static const std::string dummy_device_id = "123400000000000000000000"; static std::shared_ptr get_user(const std::shared_ptr& app) { return app->sync_manager()->get_user("user_id", ENCODE_FAKE_JWT("fake_refresh_token"), - ENCODE_FAKE_JWT("fake_access_token"), dummy_auth_url, dummy_device_id); + ENCODE_FAKE_JWT("fake_access_token"), dummy_device_id); } TEST_CASE("SyncSession: management by SyncUser", "[sync][session]") { @@ -462,7 +461,6 @@ TEST_CASE("sync: error handling", "[sync][session]") { } TEST_CASE("sync: stop policy behavior", "[sync][session]") { - const std::string dummy_auth_url = "https://realm.example.org"; if (!EventLoop::has_implementation()) return; diff --git a/test/object-store/sync/session/wait_for_completion.cpp b/test/object-store/sync/session/wait_for_completion.cpp index 701c6df37b1..3ed4d639fd5 100644 --- a/test/object-store/sync/session/wait_for_completion.cpp +++ b/test/object-store/sync/session/wait_for_completion.cpp @@ -32,7 +32,6 @@ TEST_CASE("SyncSession: wait_for_download_completion() API", "[sync][pbs][sessio if (!EventLoop::has_implementation()) return; - const std::string dummy_auth_url = "https://realm.example.org"; const std::string dummy_device_id = "123400000000000000000000"; // Disable file-related functionality and metadata functionality for testing purposes. @@ -46,7 +45,7 @@ TEST_CASE("SyncSession: wait_for_download_completion() API", "[sync][pbs][sessio SECTION("works properly when called after the session is bound") { server.start(); auto user = sync_manager->get_user("user-async-wait-download-1", ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("not_a_real_token"), dummy_auth_url, dummy_device_id); + ENCODE_FAKE_JWT("not_a_real_token"), dummy_device_id); auto session = sync_session(user, "/async-wait-download-1", [](auto, auto) {}); EventLoop::main().run_until([&] { return sessions_are_active(*session); @@ -64,7 +63,7 @@ TEST_CASE("SyncSession: wait_for_download_completion() API", "[sync][pbs][sessio server.start(); const auto user_id = "user-async-wait-download-3"; auto user = sync_manager->get_user(user_id, ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("not_a_real_token"), dummy_auth_url, dummy_device_id); + ENCODE_FAKE_JWT("not_a_real_token"), dummy_device_id); auto session = sync_session(user, "/user-async-wait-download-3", [](auto, auto) {}); EventLoop::main().run_until([&] { return sessions_are_active(*session); @@ -82,7 +81,7 @@ TEST_CASE("SyncSession: wait_for_download_completion() API", "[sync][pbs][sessio REQUIRE(handler_called == false); // Log the user back in user = sync_manager->get_user(user_id, ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("not_a_real_token"), dummy_auth_url, dummy_device_id); + ENCODE_FAKE_JWT("not_a_real_token"), dummy_device_id); EventLoop::main().run_until([&] { return sessions_are_active(*session); }); @@ -94,7 +93,7 @@ TEST_CASE("SyncSession: wait_for_download_completion() API", "[sync][pbs][sessio SECTION("aborts properly when queued and the session errors out") { auto user = sync_manager->get_user("user-async-wait-download-4", ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("not_a_real_token"), dummy_auth_url, dummy_device_id); + ENCODE_FAKE_JWT("not_a_real_token"), dummy_device_id); std::atomic error_count(0); std::shared_ptr session = sync_session(user, "/async-wait-download-4", [&](auto, auto) { ++error_count; @@ -121,7 +120,6 @@ TEST_CASE("SyncSession: wait_for_upload_completion() API", "[sync][pbs][session] if (!EventLoop::has_implementation()) return; - const std::string dummy_auth_url = "https://realm.example.org"; const std::string dummy_device_id = "123400000000000000000000"; // Disable file-related functionality and metadata functionality for testing purposes. @@ -137,7 +135,7 @@ TEST_CASE("SyncSession: wait_for_upload_completion() API", "[sync][pbs][session] SECTION("works properly when called after the session is bound") { server.start(); auto user = sync_manager->get_user("user-async-wait-upload-1", ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("not_a_real_token"), dummy_auth_url, dummy_device_id); + ENCODE_FAKE_JWT("not_a_real_token"), dummy_device_id); auto session = sync_session(user, "/async-wait-upload-1", [](auto, auto) {}); EventLoop::main().run_until([&] { return sessions_are_active(*session); @@ -155,7 +153,7 @@ TEST_CASE("SyncSession: wait_for_upload_completion() API", "[sync][pbs][session] server.start(); const auto user_id = "user-async-wait-upload-3"; auto user = sync_manager->get_user(user_id, ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("not_a_real_token"), dummy_auth_url, dummy_device_id); + ENCODE_FAKE_JWT("not_a_real_token"), dummy_device_id); auto session = sync_session(user, "/user-async-wait-upload-3", [](auto, auto) {}); EventLoop::main().run_until([&] { return sessions_are_active(*session); @@ -173,7 +171,7 @@ TEST_CASE("SyncSession: wait_for_upload_completion() API", "[sync][pbs][session] REQUIRE(handler_called == false); // Log the user back in user = sync_manager->get_user(user_id, ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("not_a_real_token"), dummy_auth_url, dummy_device_id); + ENCODE_FAKE_JWT("not_a_real_token"), dummy_device_id); EventLoop::main().run_until([&] { return sessions_are_active(*session); }); @@ -188,7 +186,7 @@ TEST_CASE("SyncSession: wait_for_upload_completion() API", "[sync][pbs][session] // SECTION("aborts properly when queued and the session errors out") { // using ProtocolError = realm::sync::ProtocolError; // auto user = SyncManager::shared().get_user("user-async-wait-upload-4", - // ENCODE_FAKE_JWT("not_a_real_token"), ENCODE_FAKE_JWT("not_a_real_token"), dummy_auth_url, + // ENCODE_FAKE_JWT("not_a_real_token"), ENCODE_FAKE_JWT("not_a_real_token"), // dummy_device_id); std::atomic error_count(0); std::shared_ptr session = // sync_session(user, "/async-wait-upload-4", // [&](auto e) { diff --git a/test/object-store/sync/sync_manager.cpp b/test/object-store/sync/sync_manager.cpp index fed59459897..db14354e232 100644 --- a/test/object-store/sync/sync_manager.cpp +++ b/test/object-store/sync/sync_manager.cpp @@ -39,13 +39,12 @@ static const std::string dummy_device_id = "123400000000000000000000"; namespace { bool validate_user_in_vector(std::vector> vector, const std::string& identity, - const std::string& provider_type, const std::string& refresh_token, - const std::string& access_token, const std::string& device_id) + const std::string& refresh_token, const std::string& access_token, + const std::string& device_id) { for (auto& user : vector) { if (user->identity() == identity && user->refresh_token() == refresh_token && - provider_type == user->provider_type() && user->access_token() == access_token && user->has_device_id() && - user->device_id() == device_id) { + user->access_token() == access_token && user->has_device_id() && user->device_id() == device_id) { return true; } } @@ -63,7 +62,6 @@ TEST_CASE("sync_manager: basic properties and APIs", "[sync][sync manager]") { } TEST_CASE("sync_manager: `path_for_realm` API", "[sync][sync manager]") { - const std::string auth_server_url = "https://realm.example.org"; const std::string raw_url = "realms://realm.example.org/a/b/~/123456/xyz"; SECTION("should work properly without metadata") { @@ -73,8 +71,7 @@ TEST_CASE("sync_manager: `path_for_realm` API", "[sync][sync manager]") { auto base_path = fs::path{tsm.base_file_path()}.make_preferred() / "mongodb-realm" / "app_id" / identity; const auto expected = base_path / "realms%3A%2F%2Frealm.example.org%2Fa%2Fb%2F%7E%2F123456%2Fxyz.realm"; auto user = tsm.app()->sync_manager()->get_user(identity, ENCODE_FAKE_JWT("dummy_token"), - ENCODE_FAKE_JWT("not_a_real_token"), auth_server_url, - dummy_device_id); + ENCODE_FAKE_JWT("not_a_real_token"), dummy_device_id); REQUIRE(user->identity() == identity); SyncConfig config(user, bson::Bson{}); REQUIRE(tsm.app()->sync_manager()->path_for_realm(config, raw_url) == expected); @@ -89,8 +86,7 @@ TEST_CASE("sync_manager: `path_for_realm` API", "[sync][sync manager]") { auto base_path = fs::path{tsm.base_file_path()}.make_preferred() / "mongodb-realm" / "app_id" / identity; const auto expected = base_path / "realms%3A%2F%2Frealm.example.org%2Fa%2Fb%2F%7E%2F123456%2Fxyz.realm"; auto user = tsm.app()->sync_manager()->get_user(identity, ENCODE_FAKE_JWT("dummy_token"), - ENCODE_FAKE_JWT("not_a_real_token"), auth_server_url, - dummy_device_id); + ENCODE_FAKE_JWT("not_a_real_token"), dummy_device_id); REQUIRE(user->identity() == identity); SyncConfig config(user, bson::Bson{}); REQUIRE(tsm.app()->sync_manager()->path_for_realm(config, raw_url) == expected); @@ -104,8 +100,7 @@ TEST_CASE("sync_manager: `path_for_realm` API", "[sync][sync manager]") { const std::string identity = random_string(10); auto base_path = fs::path{tsm.base_file_path()}.make_preferred() / "mongodb-realm" / "app_id" / identity; auto user = tsm.app()->sync_manager()->get_user(identity, ENCODE_FAKE_JWT("dummy_token"), - ENCODE_FAKE_JWT("not_a_real_token"), auth_server_url, - dummy_device_id); + ENCODE_FAKE_JWT("not_a_real_token"), dummy_device_id); // Directory should not be created until we get the path REQUIRE_DIR_PATH_DOES_NOT_EXIST(base_path); @@ -184,10 +179,6 @@ TEST_CASE("sync_manager: user state management", "[sync][sync manager]") { TestSyncManager init_sync_manager(SyncManager::MetadataMode::NoEncryption); auto sync_manager = init_sync_manager.app()->sync_manager(); - const std::string url_1 = "https://realm.example.org/1/"; - const std::string url_2 = "https://realm.example.org/2/"; - const std::string url_3 = "https://realm.example.org/3/"; - const std::string r_token_1 = ENCODE_FAKE_JWT("foo_token"); const std::string r_token_2 = ENCODE_FAKE_JWT("bar_token"); const std::string r_token_3 = ENCODE_FAKE_JWT("baz_token"); @@ -201,78 +192,66 @@ TEST_CASE("sync_manager: user state management", "[sync][sync manager]") { const std::string identity_3 = "user-baz"; SECTION("should get all users that are created during run time") { - sync_manager->get_user(identity_1, r_token_1, a_token_1, url_1, dummy_device_id); - sync_manager->get_user(identity_2, r_token_2, a_token_2, url_2, dummy_device_id); + sync_manager->get_user(identity_1, r_token_1, a_token_1, dummy_device_id); + sync_manager->get_user(identity_2, r_token_2, a_token_2, dummy_device_id); auto users = sync_manager->all_users(); REQUIRE(users.size() == 2); - CHECK(validate_user_in_vector(users, identity_1, url_1, r_token_1, a_token_1, dummy_device_id)); - CHECK(validate_user_in_vector(users, identity_2, url_2, r_token_2, a_token_2, dummy_device_id)); - } - - SECTION("should be able to distinguish users based solely on URL") { - sync_manager->get_user(identity_1, r_token_1, a_token_1, url_1, dummy_device_id); - sync_manager->get_user(identity_1, r_token_1, a_token_1, url_2, dummy_device_id); - sync_manager->get_user(identity_1, r_token_1, a_token_1, url_3, dummy_device_id); - sync_manager->get_user(identity_1, r_token_1, a_token_1, url_1, dummy_device_id); // existing - auto users = sync_manager->all_users(); - REQUIRE(users.size() == 3); - CHECK(validate_user_in_vector(users, identity_1, url_1, r_token_1, a_token_1, dummy_device_id)); - CHECK(validate_user_in_vector(users, identity_1, url_2, r_token_1, a_token_1, dummy_device_id)); - CHECK(validate_user_in_vector(users, identity_1, url_2, r_token_1, a_token_1, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_1, r_token_1, a_token_1, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_2, r_token_2, a_token_2, dummy_device_id)); } SECTION("should be able to distinguish users based solely on user ID") { - sync_manager->get_user(identity_1, r_token_1, a_token_1, url_1, dummy_device_id); - sync_manager->get_user(identity_2, r_token_1, a_token_1, url_1, dummy_device_id); - sync_manager->get_user(identity_3, r_token_1, a_token_1, url_1, dummy_device_id); - sync_manager->get_user(identity_1, r_token_1, a_token_1, url_1, dummy_device_id); // existing + sync_manager->get_user(identity_1, r_token_1, a_token_1, dummy_device_id); + sync_manager->get_user(identity_2, r_token_1, a_token_1, dummy_device_id); + sync_manager->get_user(identity_3, r_token_1, a_token_1, dummy_device_id); + sync_manager->get_user(identity_1, r_token_1, a_token_1, dummy_device_id); // existing auto users = sync_manager->all_users(); REQUIRE(users.size() == 3); - CHECK(validate_user_in_vector(users, identity_1, url_1, r_token_1, a_token_1, dummy_device_id)); - CHECK(validate_user_in_vector(users, identity_2, url_1, r_token_1, a_token_1, dummy_device_id)); - CHECK(validate_user_in_vector(users, identity_3, url_1, r_token_1, a_token_1, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_1, r_token_1, a_token_1, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_2, r_token_1, a_token_1, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_3, r_token_1, a_token_1, dummy_device_id)); } SECTION("should properly update state in response to users logging in and out") { auto r_token_3a = ENCODE_FAKE_JWT("qwerty"); auto a_token_3a = ENCODE_FAKE_JWT("ytrewq"); - auto u1 = sync_manager->get_user(identity_1, r_token_1, a_token_1, url_1, dummy_device_id); - auto u2 = sync_manager->get_user(identity_2, r_token_2, a_token_2, url_2, dummy_device_id); - auto u3 = sync_manager->get_user(identity_3, r_token_3, a_token_3, url_3, dummy_device_id); + auto u1 = sync_manager->get_user(identity_1, r_token_1, a_token_1, dummy_device_id); + auto u2 = sync_manager->get_user(identity_2, r_token_2, a_token_2, dummy_device_id); + auto u3 = sync_manager->get_user(identity_3, r_token_3, a_token_3, dummy_device_id); auto users = sync_manager->all_users(); REQUIRE(users.size() == 3); - CHECK(validate_user_in_vector(users, identity_1, url_1, r_token_1, a_token_1, dummy_device_id)); - CHECK(validate_user_in_vector(users, identity_2, url_2, r_token_2, a_token_2, dummy_device_id)); - CHECK(validate_user_in_vector(users, identity_3, url_3, r_token_3, a_token_3, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_1, r_token_1, a_token_1, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_2, r_token_2, a_token_2, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_3, r_token_3, a_token_3, dummy_device_id)); // Log out users 1 and 3 u1->log_out(); u3->log_out(); users = sync_manager->all_users(); REQUIRE(users.size() == 3); - CHECK(validate_user_in_vector(users, identity_2, url_2, r_token_2, a_token_2, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_2, r_token_2, a_token_2, dummy_device_id)); // Log user 3 back in - u3 = sync_manager->get_user(identity_3, r_token_3a, a_token_3a, url_3, dummy_device_id); + u3 = sync_manager->get_user(identity_3, r_token_3a, a_token_3a, dummy_device_id); users = sync_manager->all_users(); REQUIRE(users.size() == 3); - CHECK(validate_user_in_vector(users, identity_2, url_2, r_token_2, a_token_2, dummy_device_id)); - CHECK(validate_user_in_vector(users, identity_3, url_3, r_token_3a, a_token_3a, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_2, r_token_2, a_token_2, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_3, r_token_3a, a_token_3a, dummy_device_id)); // Log user 2 out u2->log_out(); users = sync_manager->all_users(); REQUIRE(users.size() == 3); - CHECK(validate_user_in_vector(users, identity_3, url_3, r_token_3a, a_token_3a, dummy_device_id)); + CHECK(validate_user_in_vector(users, identity_3, r_token_3a, a_token_3a, dummy_device_id)); } SECTION("should return current user that was created during run time") { auto u_null = sync_manager->get_current_user(); REQUIRE(u_null == nullptr); - auto u1 = sync_manager->get_user(identity_1, r_token_1, a_token_1, url_1, dummy_device_id); + auto u1 = sync_manager->get_user(identity_1, r_token_1, a_token_1, dummy_device_id); auto u_current = sync_manager->get_current_user(); REQUIRE(u_current == u1); - auto u2 = sync_manager->get_user(identity_2, r_token_2, a_token_2, url_2, dummy_device_id); + auto u2 = sync_manager->get_user(identity_2, r_token_2, a_token_2, dummy_device_id); // The current user has switched to return the most recently used: "u2" u_current = sync_manager->get_current_user(); REQUIRE(u_current == u2); @@ -290,9 +269,6 @@ TEST_CASE("sync_manager: persistent user state management", "[sync][sync manager // Open the metadata separately, so we can investigate it ourselves. SyncMetadataManager manager(file_manager.metadata_path(), false); - const std::string url_1 = "https://realm.example.org/1/"; - const std::string url_2 = "https://realm.example.org/2/"; - const std::string url_3 = "https://realm.example.org/3/"; const std::string r_token_1 = ENCODE_FAKE_JWT("foo_token"); const std::string r_token_2 = ENCODE_FAKE_JWT("bar_token"); const std::string r_token_3 = ENCODE_FAKE_JWT("baz_token"); @@ -305,20 +281,20 @@ TEST_CASE("sync_manager: persistent user state management", "[sync][sync manager const std::string identity_2 = "bar-1"; const std::string identity_3 = "baz-1"; // First, create a few users and add them to the metadata. - auto u1 = manager.get_or_make_user_metadata(identity_1, url_1); + auto u1 = manager.get_or_make_user_metadata(identity_1); u1->set_access_token(a_token_1); u1->set_refresh_token(r_token_1); u1->set_device_id(dummy_device_id); - auto u2 = manager.get_or_make_user_metadata(identity_2, url_2); + auto u2 = manager.get_or_make_user_metadata(identity_2); u2->set_access_token(a_token_2); u2->set_refresh_token(r_token_2); u2->set_device_id(dummy_device_id); - auto u3 = manager.get_or_make_user_metadata(identity_3, url_3); + auto u3 = manager.get_or_make_user_metadata(identity_3); u3->set_access_token(a_token_3); u3->set_refresh_token(r_token_3); u3->set_device_id(dummy_device_id); // The fourth user is an "invalid" user: no token, so shouldn't show up. - auto u_invalid = manager.get_or_make_user_metadata("invalid_user", url_1); + auto u_invalid = manager.get_or_make_user_metadata("invalid_user"); REQUIRE(manager.all_unmarked_users().size() == 4); SECTION("they should be added to the active users list when metadata is enabled") { @@ -326,9 +302,9 @@ TEST_CASE("sync_manager: persistent user state management", "[sync][sync manager TestSyncManager tsm(config); auto users = tsm.app()->sync_manager()->all_users(); REQUIRE(users.size() == 3); - REQUIRE(validate_user_in_vector(users, identity_1, url_1, r_token_1, a_token_1, dummy_device_id)); - REQUIRE(validate_user_in_vector(users, identity_2, url_2, r_token_2, a_token_2, dummy_device_id)); - REQUIRE(validate_user_in_vector(users, identity_3, url_3, r_token_3, a_token_3, dummy_device_id)); + REQUIRE(validate_user_in_vector(users, identity_1, r_token_1, a_token_1, dummy_device_id)); + REQUIRE(validate_user_in_vector(users, identity_2, r_token_2, a_token_2, dummy_device_id)); + REQUIRE(validate_user_in_vector(users, identity_3, r_token_3, a_token_3, dummy_device_id)); } SECTION("they should not be added to the active users list when metadata is disabled") { @@ -348,20 +324,23 @@ TEST_CASE("sync_manager: persistent user state management", "[sync][sync manager std::vector paths_under_test; SECTION("when users are marked") { - const std::string provider_type = "user-pass"; const std::string identity_1 = "foo-2"; const std::string identity_2 = "bar-2"; const std::string identity_3 = "baz-2"; // Create the user metadata. - auto u1 = manager.get_or_make_user_metadata(identity_1, provider_type); - auto u2 = manager.get_or_make_user_metadata(identity_2, provider_type); + auto u1 = manager.get_or_make_user_metadata(identity_1); + auto u2 = manager.get_or_make_user_metadata(identity_2); // Don't mark this user for deletion. - auto u3 = manager.get_or_make_user_metadata(identity_3, provider_type); + auto u3 = manager.get_or_make_user_metadata(identity_3); + + u1->set_legacy_identities({"legacy1"}); + u2->set_legacy_identities({"legacy2"}); + u3->set_legacy_identities({"legacy3"}); { auto expected_u1_path = [&](const bson::Bson& partition) { - return ExpectedRealmPaths(tsm.base_file_path(), app_id, u1->identity(), u1->local_uuid(), + return ExpectedRealmPaths(tsm.base_file_path(), app_id, u1->identity(), u1->legacy_identities(), partition.to_string()); }; bson::Bson partition = "partition1"; @@ -394,12 +373,9 @@ TEST_CASE("sync_manager: persistent user state management", "[sync][sync manager auto sync_manager = tsm.app()->sync_manager(); // Pre-populate the user directories. - auto user1 = - sync_manager->get_user(u1->identity(), r_token_1, a_token_1, u1->provider_type(), dummy_device_id); - auto user2 = - sync_manager->get_user(u2->identity(), r_token_2, a_token_2, u2->provider_type(), dummy_device_id); - auto user3 = - sync_manager->get_user(u3->identity(), r_token_3, a_token_3, u3->provider_type(), dummy_device_id); + auto user1 = sync_manager->get_user(u1->identity(), r_token_1, a_token_1, dummy_device_id); + auto user2 = sync_manager->get_user(u2->identity(), r_token_2, a_token_2, dummy_device_id); + auto user3 = sync_manager->get_user(u3->identity(), r_token_3, a_token_3, dummy_device_id); for (auto& dir : dirs_to_create) { try_make_dir(dir); } @@ -437,7 +413,7 @@ TEST_CASE("sync_manager: persistent user state management", "[sync][sync manager TestSyncManager tsm(config); auto users = tsm.app()->sync_manager()->all_users(); REQUIRE(users.size() == 1); - REQUIRE(validate_user_in_vector(users, identity_3, provider_type, r_token_3, a_token_3, dummy_device_id)); + REQUIRE(validate_user_in_vector(users, identity_3, r_token_3, a_token_3, dummy_device_id)); REQUIRE_REALM_DOES_NOT_EXIST(paths[0]); REQUIRE_REALM_DOES_NOT_EXIST(paths[1]); REQUIRE_REALM_DOES_NOT_EXIST(paths[2]); @@ -482,16 +458,13 @@ TEST_CASE("sync_manager: file actions", "[sync][sync manager]") { const std::string uuid_3 = "uuid-baz-1"; const std::string uuid_4 = "uuid-baz-2"; - const std::string local_uuid_1 = "foo-1"; - const std::string local_uuid_2 = "bar-1"; - const std::string local_uuid_3 = "baz-1"; - const std::string local_uuid_4 = "baz-2"; + const std::vector legacy_identities; // Realm paths - const std::string realm_path_1 = file_manager.realm_file_path(uuid_1, local_uuid_1, realm_url, partition); - const std::string realm_path_2 = file_manager.realm_file_path(uuid_2, local_uuid_2, realm_url, partition); - const std::string realm_path_3 = file_manager.realm_file_path(uuid_3, local_uuid_3, realm_url, partition); - const std::string realm_path_4 = file_manager.realm_file_path(uuid_4, local_uuid_4, realm_url, partition); + const std::string realm_path_1 = file_manager.realm_file_path(uuid_1, legacy_identities, realm_url, partition); + const std::string realm_path_2 = file_manager.realm_file_path(uuid_2, legacy_identities, realm_url, partition); + const std::string realm_path_3 = file_manager.realm_file_path(uuid_3, legacy_identities, realm_url, partition); + const std::string realm_path_4 = file_manager.realm_file_path(uuid_4, legacy_identities, realm_url, partition); // On windows you can't delete a realm if the file is open elsewhere. #ifdef _WIN32 @@ -591,7 +564,7 @@ TEST_CASE("sync_manager: file actions", "[sync][sync manager]") { SECTION("should copy the Realm to the recovery_directory_path") { const std::string identity = "b241922032489d4836ecd0c82d0445f0"; - const auto realm_base_path = file_manager.realm_file_path(identity, "", "realmtasks", partition); + const auto realm_base_path = file_manager.realm_file_path(identity, {}, "realmtasks", partition); std::string recovery_path = util::reserve_unique_file_name( file_manager.recovery_directory_path(), util::create_timestamped_template("recovered_realm")); create_dummy_realm(realm_base_path); @@ -738,9 +711,9 @@ TEST_CASE("sync_manager: set_session_multiplexing", "[sync][sync manager]") { sync_manager->set_session_multiplexing(sync_multiplexing_allowed); auto user_1 = sync_manager->get_user("user-name-1", ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("samesies"), "https://realm.example.org", dummy_device_id); + ENCODE_FAKE_JWT("samesies"), dummy_device_id); auto user_2 = sync_manager->get_user("user-name-2", ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("samesies"), "https://realm.example.org", dummy_device_id); + ENCODE_FAKE_JWT("samesies"), dummy_device_id); SyncTestFile file_1(user_1, "partition1", util::none); SyncTestFile file_2(user_1, "partition2", util::none); @@ -784,7 +757,7 @@ TEST_CASE("sync_manager: has_existing_sessions", "[sync][sync manager][active se std::atomic error_handler_invoked(false); Realm::Config config; auto user = sync_manager->get_user("user-name", ENCODE_FAKE_JWT("not_a_real_token"), ENCODE_FAKE_JWT("samesies"), - "https://realm.example.org", dummy_device_id); + dummy_device_id); auto create_session = [&](SyncSessionStopPolicy stop_policy) { std::shared_ptr session = sync_session( user, "/test-dying-state", diff --git a/test/object-store/sync/user.cpp b/test/object-store/sync/user.cpp index d14e48f33bc..457b2873028 100644 --- a/test/object-store/sync/user.cpp +++ b/test/object-store/sync/user.cpp @@ -40,14 +40,12 @@ TEST_CASE("sync_user: SyncManager `get_user()` API", "[sync][user]") { const std::string identity = "sync_test_identity"; const std::string refresh_token = ENCODE_FAKE_JWT("1234567890-fake-refresh-token"); const std::string access_token = ENCODE_FAKE_JWT("1234567890-fake-access-token"); - const std::string server_url = "https://realm.example.org"; SECTION("properly creates a new normal user") { - auto user = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); + auto user = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); REQUIRE(user); // The expected state for a newly created user: REQUIRE(user->identity() == identity); - REQUIRE(user->provider_type() == server_url); REQUIRE(user->refresh_token() == refresh_token); REQUIRE(user->access_token() == access_token); REQUIRE(user->state() == SyncUser::State::LoggedIn); @@ -57,13 +55,12 @@ TEST_CASE("sync_user: SyncManager `get_user()` API", "[sync][user]") { const std::string second_refresh_token = ENCODE_FAKE_JWT("0987654321-fake-refresh-token"); const std::string second_access_token = ENCODE_FAKE_JWT("0987654321-fake-access-token"); - auto first = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); + auto first = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); REQUIRE(first); REQUIRE(first->identity() == identity); REQUIRE(first->refresh_token() == refresh_token); // Get the user again, but with a different token. - auto second = - sync_manager->get_user(identity, second_refresh_token, second_access_token, server_url, dummy_device_id); + auto second = sync_manager->get_user(identity, second_refresh_token, second_access_token, dummy_device_id); REQUIRE(second == first); REQUIRE(second->identity() == identity); REQUIRE(second->access_token() == second_access_token); @@ -74,13 +71,12 @@ TEST_CASE("sync_user: SyncManager `get_user()` API", "[sync][user]") { const std::string second_refresh_token = ENCODE_FAKE_JWT("0987654321-fake-refresh-token"); const std::string second_access_token = ENCODE_FAKE_JWT("0987654321-fake-access-token"); - auto first = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); + auto first = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); REQUIRE(first->identity() == identity); first->log_out(); REQUIRE(first->state() == SyncUser::State::LoggedOut); // Get the user again, with a new token. - auto second = - sync_manager->get_user(identity, second_refresh_token, second_access_token, server_url, dummy_device_id); + auto second = sync_manager->get_user(identity, second_refresh_token, second_access_token, dummy_device_id); REQUIRE(second == first); REQUIRE(second->identity() == identity); REQUIRE(second->refresh_token() == second_refresh_token); @@ -94,11 +90,10 @@ TEST_CASE("sync_user: update state and tokens", "[sync][user]") { const std::string identity = "sync_test_identity"; const std::string refresh_token = ENCODE_FAKE_JWT("fake-refresh-token-1"); const std::string access_token = ENCODE_FAKE_JWT("fake-access-token-1"); - const std::string server_url = "https://realm.example.org"; const std::string second_refresh_token = ENCODE_FAKE_JWT("fake-refresh-token-4"); const std::string second_access_token = ENCODE_FAKE_JWT("fake-access-token-4"); - auto user = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); + auto user = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); REQUIRE(user->is_logged_in()); REQUIRE(user->refresh_token() == refresh_token); @@ -123,7 +118,6 @@ TEST_CASE("sync_user: SyncManager `get_existing_logged_in_user()` API", "[sync][ const std::string identity = "sync_test_identity"; const std::string refresh_token = ENCODE_FAKE_JWT("1234567890-fake-refresh-token"); const std::string access_token = ENCODE_FAKE_JWT("1234567890-fake-access-token"); - const std::string server_url = "https://realm.example.org"; SECTION("properly returns a null pointer when called for a non-existent user") { std::shared_ptr user = sync_manager->get_existing_logged_in_user(identity); @@ -131,7 +125,7 @@ TEST_CASE("sync_user: SyncManager `get_existing_logged_in_user()` API", "[sync][ } SECTION("properly returns an existing logged-in user") { - auto first = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); + auto first = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); REQUIRE(first->identity() == identity); REQUIRE(first->state() == SyncUser::State::LoggedIn); REQUIRE(first->device_id() == dummy_device_id); @@ -142,7 +136,7 @@ TEST_CASE("sync_user: SyncManager `get_existing_logged_in_user()` API", "[sync][ } SECTION("properly returns a null pointer for a logged-out user") { - auto first = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); + auto first = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); first->log_out(); REQUIRE(first->identity() == identity); REQUIRE(first->state() == SyncUser::State::LoggedOut); @@ -158,10 +152,9 @@ TEST_CASE("sync_user: logout", "[sync][user]") { const std::string identity = "sync_test_identity"; const std::string refresh_token = ENCODE_FAKE_JWT("1234567890-fake-refresh-token"); const std::string access_token = ENCODE_FAKE_JWT("1234567890-fake-access-token"); - const std::string server_url = "https://realm.example.org"; SECTION("properly changes the state of the user object") { - auto user = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); + auto user = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); REQUIRE(user->state() == SyncUser::State::LoggedIn); user->log_out(); REQUIRE(user->state() == SyncUser::State::LoggedOut); @@ -179,15 +172,13 @@ TEST_CASE("sync_user: user persistence", "[sync][user]") { const std::string identity = "test_identity_1"; const std::string refresh_token = ENCODE_FAKE_JWT("r-token-1"); const std::string access_token = ENCODE_FAKE_JWT("a-token-1"); - const std::string server_url = "https://realm.example.org/1/"; const std::vector identities{{"12345", "test_case_provider"}}; - auto user = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); + auto user = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); user->update_identities(identities); // Now try to pull the user out of the shadow manager directly. - auto metadata = manager.get_or_make_user_metadata(identity, server_url, false); + auto metadata = manager.get_or_make_user_metadata(identity, false); REQUIRE((bool)metadata); REQUIRE(metadata->is_valid()); - REQUIRE(metadata->provider_type() == server_url); REQUIRE(metadata->access_token() == access_token); REQUIRE(metadata->refresh_token() == refresh_token); REQUIRE(metadata->device_id() == dummy_device_id); @@ -198,16 +189,14 @@ TEST_CASE("sync_user: user persistence", "[sync][user]") { const std::string identity = "test_identity_1"; const std::string refresh_token = ENCODE_FAKE_JWT("r-token-1"); const std::string access_token = ENCODE_FAKE_JWT("a-token-1"); - const std::string server_url = "https://realm.example.org/1/"; const std::vector identities{{"12345", "test_case_provider"}}; - auto user = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); + auto user = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); user->update_identities(identities); user->log_out(); // Now try to pull the user out of the shadow manager directly. - auto metadata = manager.get_or_make_user_metadata(identity, server_url, false); + auto metadata = manager.get_or_make_user_metadata(identity, false); REQUIRE((bool)metadata); REQUIRE(metadata->is_valid()); - REQUIRE(metadata->provider_type() == server_url); REQUIRE(metadata->access_token() == ""); REQUIRE(metadata->refresh_token() == ""); REQUIRE(metadata->device_id() == dummy_device_id); @@ -220,16 +209,15 @@ TEST_CASE("sync_user: user persistence", "[sync][user]") { const std::string identity = "test_identity_2"; const std::string refresh_token = ENCODE_FAKE_JWT("r_token-2a"); const std::string access_token = ENCODE_FAKE_JWT("a_token-1a"); - const std::string server_url = "https://realm.example.org/2/"; // Create the user and validate it. - auto first = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); - auto first_metadata = manager.get_or_make_user_metadata(identity, server_url, false); + auto first = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); + auto first_metadata = manager.get_or_make_user_metadata(identity, false); REQUIRE(first_metadata->is_valid()); REQUIRE(first_metadata->access_token() == access_token); const std::string token_2 = ENCODE_FAKE_JWT("token-2b"); // Update the user. - auto second = sync_manager->get_user(identity, refresh_token, token_2, server_url, dummy_device_id); - auto second_metadata = manager.get_or_make_user_metadata(identity, server_url, false); + auto second = sync_manager->get_user(identity, refresh_token, token_2, dummy_device_id); + auto second_metadata = manager.get_or_make_user_metadata(identity, false); REQUIRE(second_metadata->is_valid()); REQUIRE(second_metadata->access_token() == token_2); } @@ -238,9 +226,8 @@ TEST_CASE("sync_user: user persistence", "[sync][user]") { const std::string identity = "test_identity_3"; const std::string refresh_token = ENCODE_FAKE_JWT("r-token-3"); const std::string access_token = ENCODE_FAKE_JWT("a-token-3"); - const std::string provider_type = app::IdentityProviderGoogle; // Create the user and validate it. - auto user = sync_manager->get_user(identity, refresh_token, access_token, provider_type, dummy_device_id); + auto user = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); auto marked_users = manager.all_users_marked_for_removal(); REQUIRE(marked_users.size() == 0); // Log out the user. @@ -253,9 +240,9 @@ TEST_CASE("sync_user: user persistence", "[sync][user]") { const std::string identity = "test_identity_3"; const std::string refresh_token = ENCODE_FAKE_JWT("r-token-3"); const std::string access_token = ENCODE_FAKE_JWT("a-token-3"); - const std::string provider_type = app::IdentityProviderAnonymous; // Create the user and validate it. - auto user = sync_manager->get_user(identity, refresh_token, access_token, provider_type, dummy_device_id); + auto user = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); + user->update_identities({{"id", app::IdentityProviderAnonymous}}); auto marked_users = manager.all_users_marked_for_removal(); REQUIRE(marked_users.size() == 0); // Log out the user. @@ -267,16 +254,15 @@ TEST_CASE("sync_user: user persistence", "[sync][user]") { const std::string identity = "test_identity_3"; const std::string refresh_token = ENCODE_FAKE_JWT("r-token-4a"); const std::string access_token = ENCODE_FAKE_JWT("a-token-4a"); - const std::string provider_type = app::IdentityProviderApple; // Create the user and log it out. - auto first = sync_manager->get_user(identity, refresh_token, access_token, provider_type, dummy_device_id); + auto first = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); first->log_out(); REQUIRE(sync_manager->all_users().size() == 1); REQUIRE(sync_manager->all_users()[0]->state() == SyncUser::State::LoggedOut); // Log the user back in. const std::string r_token_2 = ENCODE_FAKE_JWT("r-token-4b"); const std::string a_token_2 = ENCODE_FAKE_JWT("atoken-4b"); - auto second = sync_manager->get_user(identity, r_token_2, a_token_2, provider_type, dummy_device_id); + auto second = sync_manager->get_user(identity, r_token_2, a_token_2, dummy_device_id); REQUIRE(sync_manager->all_users().size() == 1); REQUIRE(sync_manager->all_users()[0]->state() == SyncUser::State::LoggedIn); } @@ -285,9 +271,8 @@ TEST_CASE("sync_user: user persistence", "[sync][user]") { const std::string identity = "test_identity_3"; const std::string refresh_token = ENCODE_FAKE_JWT("r-token-3"); const std::string access_token = ENCODE_FAKE_JWT("a-token-3"); - const std::string provider_type = app::IdentityProviderAnonymous; // Create the user and validate it. - auto user = sync_manager->get_user(identity, refresh_token, access_token, provider_type, dummy_device_id); + auto user = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); sync_manager->set_current_user(identity); REQUIRE(sync_manager->get_current_user() == user); REQUIRE(sync_manager->all_users().size() == 1); diff --git a/test/object-store/util/sync/sync_test_utils.cpp b/test/object-store/util/sync/sync_test_utils.cpp index 416c09d9dc1..d2ba5c35f5b 100644 --- a/test/object-store/util/sync/sync_test_utils.cpp +++ b/test/object-store/util/sync/sync_test_utils.cpp @@ -52,12 +52,11 @@ std::ostream& operator<<(std::ostream& os, util::Optional error) return os; } -bool results_contains_user(SyncUserMetadataResults& results, const std::string& identity, - const std::string& provider_type) +bool results_contains_user(SyncUserMetadataResults& results, const std::string& identity) { for (size_t i = 0; i < results.size(); i++) { auto this_result = results.get(i); - if (this_result.identity() == identity && this_result.provider_type() == provider_type) { + if (this_result.identity() == identity) { return true; } } @@ -122,7 +121,7 @@ auto do_hash = [](const std::string& name) -> std::string { }; ExpectedRealmPaths::ExpectedRealmPaths(const std::string& base_path, const std::string& app_id, - const std::string& identity, const std::string& local_identity, + const std::string& identity, const std::vector& legacy_identities, const std::string& partition) { // This is copied from SyncManager.cpp string_from_partition() in order to prevent @@ -158,6 +157,10 @@ ExpectedRealmPaths::ExpectedRealmPaths(const std::string& base_path, const std:: const auto preferred_name = manager_path / identity / clean_name; current_preferred_path = preferred_name.string() + ".realm"; fallback_hashed_path = (manager_path / do_hash(preferred_name.string())).string() + ".realm"; + + if (legacy_identities.size() < 1) + return; + auto& local_identity = legacy_identities[0]; legacy_sync_directories_to_make.push_back((manager_path / local_identity).string()); std::string encoded_partition = util::make_percent_encoded_string(partition); legacy_local_id_path = (manager_path / local_identity / encoded_partition).concat(".realm").string(); diff --git a/test/object-store/util/sync/sync_test_utils.hpp b/test/object-store/util/sync/sync_test_utils.hpp index f1a60f5ab1f..4a81eca8677 100644 --- a/test/object-store/util/sync/sync_test_utils.hpp +++ b/test/object-store/util/sync/sync_test_utils.hpp @@ -46,8 +46,7 @@ namespace realm { -bool results_contains_user(SyncUserMetadataResults& results, const std::string& identity, - const std::string& auth_server); +bool results_contains_user(SyncUserMetadataResults& results, const std::string& identity); bool results_contains_original_name(SyncFileActionMetadataResults& results, const std::string& original_name); void timed_wait_for(util::FunctionRef condition, @@ -119,7 +118,7 @@ util::Future wait_for_future(util::Future&& input, std::chrono::millisecon struct ExpectedRealmPaths { ExpectedRealmPaths(const std::string& base_path, const std::string& app_id, const std::string& user_identity, - const std::string& local_identity, const std::string& partition); + const std::vector& legacy_identities, const std::string& partition); std::string current_preferred_path; std::string fallback_hashed_path; std::string legacy_local_id_path; diff --git a/test/object-store/util/test_file.cpp b/test/object-store/util/test_file.cpp index 2e518b8b9d3..c023100c360 100644 --- a/test/object-store/util/test_file.cpp +++ b/test/object-store/util/test_file.cpp @@ -131,8 +131,7 @@ static const std::string fake_device_id = "123400000000000000000000"; static std::shared_ptr get_fake_user(app::App& app, const std::string& user_name) { - return app.sync_manager()->get_user(user_name, fake_refresh_token, fake_access_token, app.base_url(), - fake_device_id); + return app.sync_manager()->get_user(user_name, fake_refresh_token, fake_access_token, fake_device_id); } SyncTestFile::SyncTestFile(std::shared_ptr app, std::string name, std::string user_name) diff --git a/test/object-store/util/unit_test_transport.cpp b/test/object-store/util/unit_test_transport.cpp new file mode 100644 index 00000000000..ee3197351f7 --- /dev/null +++ b/test/object-store/util/unit_test_transport.cpp @@ -0,0 +1,228 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2023 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. +// +//////////////////////////////////////////////////////////////////////////// + +#include "util/unit_test_transport.hpp" + +#include +#include +#include +#include + +#include + +using namespace realm; +using namespace realm::app; + +std::string UnitTestTransport::access_token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + "eyJleHAiOjE1ODE1MDc3OTYsImlhdCI6MTU4MTUwNTk5NiwiaXNzIjoiNWU0M2RkY2M2MzZlZTEwNmVhYTEyYmRjIiwic3RpdGNoX2RldklkIjoi" + "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWUxNDk5MTNjOTBiNGFmMGViZTkzNTI3Iiwic3ViIjoiNWU0M2Rk" + "Y2M2MzZlZTEwNmVhYTEyYmRhIiwidHlwIjoiYWNjZXNzIn0.0q3y9KpFxEnbmRwahvjWU1v9y1T1s3r2eozu93vMc3s"; +const std::string UnitTestTransport::api_key = "lVRPQVYBJSIbGos2ZZn0mGaIq1SIOsGaZ5lrcp8bxlR5jg4OGuGwQq1GkektNQ3i"; +const std::string UnitTestTransport::api_key_id = "5e5e6f0abe4ae2a2c2c2d329"; +const std::string UnitTestTransport::api_key_name = "some_api_key_name"; +const std::string UnitTestTransport::auth_route = "https://mongodb.com/unittests"; +const std::string UnitTestTransport::identity_0_id = "Ursus arctos isabellinus"; +const std::string UnitTestTransport::identity_1_id = "Ursus arctos horribilis"; + +UnitTestTransport::UnitTestTransport(const std::string& provider_type, uint64_t request_timeout) + : m_provider_type(provider_type) + , m_request_timeout(request_timeout) + , m_options({{"device", + {{"appId", "app_id"}, + {"platform", util::get_library_platform()}, + {"platformVersion", "Object Store Test Platform Version"}, + {"sdk", "SDK Name"}, + {"sdkVersion", "SDK Version"}, + {"cpuArch", util::get_library_cpu_arch()}, + {"deviceName", "Device Name"}, + {"deviceVersion", "Device Version"}, + {"frameworkName", "Framework Name"}, + {"frameworkVersion", "Framework Version"}, + {"coreVersion", REALM_VERSION_STRING}, + {"bundleId", "Bundle Id"}}}}) +{ +} + +void UnitTestTransport::handle_profile(const Request& request, + util::UniqueFunction&& completion) +{ + CHECK(request.method == HttpMethod::get); + auto content_type = AppUtils::find_header("Content-Type", request.headers); + CHECK(content_type); + CHECK(content_type->second == "application/json;charset=utf-8"); + auto authorization = AppUtils::find_header("Authorization", request.headers); + CHECK(authorization); + CHECK(authorization->second == "Bearer " + access_token); + CHECK(request.body.empty()); + CHECK(request.timeout_ms == m_request_timeout); + + std::string user_id = util::uuid_string(); + std::string response; + if (m_provider_type == IdentityProviderAnonymous) { + response = nlohmann::json({{"user_id", user_id}, + {"identities", {{{"id", identity_0_id}, {"provider_type", m_provider_type}}}}, + {"data", m_user_profile}}) + .dump(); + } + else { + response = nlohmann::json({{"user_id", user_id}, + {"identities", + {{{"id", identity_0_id}, {"provider_type", m_provider_type}}, + {{"id", identity_1_id}, {"provider_type", "lol_wut"}}}}, + {"data", m_user_profile}}) + .dump(); + } + + completion(Response{200, 0, {}, response}); +} + +void UnitTestTransport::handle_login(const Request& request, util::UniqueFunction&& completion) +{ + CHECK(request.method == HttpMethod::post); + auto item = AppUtils::find_header("Content-Type", request.headers); + CHECK(item); + CHECK(item->second == "application/json;charset=utf-8"); + CHECK(nlohmann::json::parse(request.body)["options"] == m_options); + + CHECK(request.timeout_ms == m_request_timeout); + + std::string response = nlohmann::json({{"access_token", access_token}, + {"refresh_token", access_token}, + {"user_id", util::uuid_string()}, + {"device_id", "Panda Bear"}}) + .dump(); + + completion(Response{200, 0, {}, response}); +} + +void UnitTestTransport::handle_location(const Request& request, + util::UniqueFunction&& completion) +{ + CHECK(request.method == HttpMethod::get); + CHECK(request.timeout_ms == m_request_timeout); + + std::string response = nlohmann::json({{"deployment_model", "this"}, + {"hostname", "field"}, + {"ws_hostname", "shouldn't"}, + {"location", "matter"}}) + .dump(); + + completion(Response{200, 0, {}, response}); +} + +void UnitTestTransport::handle_create_api_key(const Request& request, + util::UniqueFunction&& completion) +{ + CHECK(request.method == HttpMethod::post); + auto item = AppUtils::find_header("Content-Type", request.headers); + CHECK(item); + CHECK(item->second == "application/json;charset=utf-8"); + CHECK(nlohmann::json::parse(request.body) == nlohmann::json({{"name", api_key_name}})); + CHECK(request.timeout_ms == m_request_timeout); + + std::string response = + nlohmann::json({{"_id", api_key_id}, {"key", api_key}, {"name", api_key_name}, {"disabled", false}}).dump(); + + completion(Response{200, 0, {}, response}); +} + +void UnitTestTransport::handle_fetch_api_key(const Request& request, + util::UniqueFunction&& completion) +{ + CHECK(request.method == HttpMethod::get); + auto item = AppUtils::find_header("Content-Type", request.headers); + CHECK(item); + CHECK(item->second == "application/json;charset=utf-8"); + + CHECK(request.body == ""); + CHECK(request.timeout_ms == m_request_timeout); + + std::string response = nlohmann::json({{"_id", api_key_id}, {"name", api_key_name}, {"disabled", false}}).dump(); + + completion(Response{200, 0, {}, response}); +} + +void UnitTestTransport::handle_fetch_api_keys(const Request& request, + util::UniqueFunction&& completion) +{ + CHECK(request.method == HttpMethod::get); + auto item = AppUtils::find_header("Content-Type", request.headers); + CHECK(item); + CHECK(item->second == "application/json;charset=utf-8"); + + CHECK(request.body == ""); + CHECK(request.timeout_ms == m_request_timeout); + + auto elements = std::vector(); + for (int i = 0; i < 2; i++) { + elements.push_back({{"_id", api_key_id}, {"name", api_key_name}, {"disabled", false}}); + } + + completion(Response{200, 0, {}, nlohmann::json(elements).dump()}); +} + +void UnitTestTransport::handle_token_refresh(const Request& request, + util::UniqueFunction&& completion) +{ + CHECK(request.method == HttpMethod::post); + auto item = AppUtils::find_header("Content-Type", request.headers); + CHECK(item); + CHECK(item->second == "application/json;charset=utf-8"); + + CHECK(request.body == ""); + CHECK(request.timeout_ms == m_request_timeout); + + auto elements = std::vector(); + nlohmann::json json{{"access_token", access_token}}; + + completion(Response{200, 0, {}, json.dump()}); +} + +void UnitTestTransport::send_request_to_server(const Request& request, + util::UniqueFunction&& completion) +{ + if (request.url.find("/login") != std::string::npos) { + handle_login(request, std::move(completion)); + } + else if (request.url.find("/profile") != std::string::npos) { + handle_profile(request, std::move(completion)); + } + else if (request.url.find("/session") != std::string::npos && request.method != HttpMethod::post) { + completion(Response{200, 0, {}, ""}); + } + else if (request.url.find("/api_keys") != std::string::npos && request.method == HttpMethod::post) { + handle_create_api_key(request, std::move(completion)); + } + else if (request.url.find(util::format("/api_keys/%1", api_key_id)) != std::string::npos && + request.method == HttpMethod::get) { + handle_fetch_api_key(request, std::move(completion)); + } + else if (request.url.find("/api_keys") != std::string::npos && request.method == HttpMethod::get) { + handle_fetch_api_keys(request, std::move(completion)); + } + else if (request.url.find("/session") != std::string::npos && request.method == HttpMethod::post) { + handle_token_refresh(request, std::move(completion)); + } + else if (request.url.find("/location") != std::string::npos && request.method == HttpMethod::get) { + handle_location(request, std::move(completion)); + } + else { + completion(Response{200, 0, {}, "something arbitrary"}); + } +} diff --git a/test/object-store/util/unit_test_transport.hpp b/test/object-store/util/unit_test_transport.hpp new file mode 100644 index 00000000000..9b29525ceaf --- /dev/null +++ b/test/object-store/util/unit_test_transport.hpp @@ -0,0 +1,83 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2023 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. +// +//////////////////////////////////////////////////////////////////////////// + +#include + +#include + +class UnitTestTransport : public realm::app::GenericNetworkTransport { +public: + UnitTestTransport(const std::string& provider_type, uint64_t request_timeout); + + explicit UnitTestTransport(const std::string& provider_type = "anon-user") + : UnitTestTransport(provider_type, 60000) + { + } + explicit UnitTestTransport(uint64_t request_timeout) + : UnitTestTransport("anon-user", request_timeout) + { + } + + static std::string access_token; + + static const std::string api_key; + static const std::string api_key_id; + static const std::string api_key_name; + static const std::string auth_route; + static const std::string identity_0_id; + static const std::string identity_1_id; + + void set_provider_type(const std::string& provider_type) + { + m_provider_type = provider_type; + } + + void set_profile(nlohmann::json profile) + { + m_user_profile = std::move(profile); + } + + void set_expected_options(nlohmann::json options) + { + m_options = std::move(options); + } + + void send_request_to_server(const realm::app::Request& request, + realm::util::UniqueFunction&& completion) override; + +private: + std::string m_provider_type; + uint64_t m_request_timeout = 60000; + nlohmann::json m_user_profile = nlohmann::json::object(); + nlohmann::json m_options; + + void handle_profile(const realm::app::Request& request, + realm::util::UniqueFunction&& completion); + void handle_login(const realm::app::Request& request, + realm::util::UniqueFunction&& completion); + void handle_location(const realm::app::Request& request, + realm::util::UniqueFunction&& completion); + void handle_create_api_key(const realm::app::Request& request, + realm::util::UniqueFunction&& completion); + void handle_fetch_api_key(const realm::app::Request& request, + realm::util::UniqueFunction&& completion); + void handle_fetch_api_keys(const realm::app::Request& request, + realm::util::UniqueFunction&& completion); + void handle_token_refresh(const realm::app::Request& request, + realm::util::UniqueFunction&& completion); +}; From a91211943fa72eead34566df8e660c7c3c018642 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Tue, 1 Aug 2023 11:40:51 -0700 Subject: [PATCH 21/34] Fix a data race in a test --- src/realm/object-store/sync/sync_session.cpp | 2 +- test/object-store/sync/flx_sync.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index 185b0e3a58e..9445af465f5 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -740,7 +740,7 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) return; } - // Dont't bother invoking m_config.error_handler if the sync is inactive. + // Don't bother invoking m_config.error_handler if the sync is inactive. // It does not make sense to call the handler when the session is closed. if (m_state == State::Inactive || m_state == State::Paused) { return; diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index bc4a402ba49..1ca2bc301f6 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -612,7 +612,8 @@ TEST_CASE("flx: client reset", "[sync][flx][client reset][baas]") { util::try_remove_dir_recursive(fresh_path); auto config_copy = config_local; - config_local.sync_config->error_handler = nullptr; + config_copy.sync_config = std::make_shared(*config_copy.sync_config); + config_copy.sync_config->error_handler = nullptr; auto&& [reset_future, reset_handler] = make_client_reset_handler(); config_copy.sync_config->notify_after_client_reset = reset_handler; From 795433407062e039a35bcb25923ce0362be3582a Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Tue, 1 Aug 2023 13:04:59 -0700 Subject: [PATCH 22/34] Fix a lock-order inversion between SyncUser and SyncManager --- src/realm/object-store/sync/sync_manager.cpp | 39 +++++++++----------- src/realm/object-store/sync/sync_manager.hpp | 2 +- src/realm/object-store/sync/sync_user.cpp | 2 +- test/object-store/sync/app.cpp | 7 ++-- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/realm/object-store/sync/sync_manager.cpp b/src/realm/object-store/sync/sync_manager.cpp index 9e73196d3a6..f804e79f948 100644 --- a/src/realm/object-store/sync/sync_manager.cpp +++ b/src/realm/object-store/sync/sync_manager.cpp @@ -417,39 +417,36 @@ std::shared_ptr SyncManager::get_current_user() const return cur_user_ident ? get_user_for_identity(*cur_user_ident) : nullptr; } -void SyncManager::log_out_user(const std::string& user_id) +void SyncManager::log_out_user(const SyncUser& user) { util::CheckedLockGuard lock(m_user_mutex); // Move this user to the end of the vector - if (m_users.size() > 1) { - auto it = std::find_if(m_users.begin(), m_users.end(), [user_id](const auto& user) { - return user->identity() == user_id; - }); + auto user_pos = std::partition(m_users.begin(), m_users.end(), [&](auto& u) { + return u.get() != &user; + }); - if (it != m_users.end()) - std::rotate(it, it + 1, m_users.end()); - } + auto active_user = std::find_if(m_users.begin(), user_pos, [](auto& u) { + return u->state() == SyncUser::State::LoggedIn; + }); util::CheckedLockGuard fs_lock(m_file_system_mutex); - bool was_active = (m_current_user && m_current_user->identity() == user_id) || - (m_metadata_manager && m_metadata_manager->get_current_user_identity() == user_id); + bool was_active = m_current_user.get() == &user || + (m_metadata_manager && m_metadata_manager->get_current_user_identity() == user.identity()); if (!was_active) return; // Set the current active user to the next logged in user, or null if none - for (auto& user : m_users) { - if (user->state() == SyncUser::State::LoggedIn) { - if (m_metadata_manager) - m_metadata_manager->set_current_user_identity(user->identity()); - m_current_user = user; - return; - } + if (active_user != user_pos) { + m_current_user = *active_user; + if (m_metadata_manager) + m_metadata_manager->set_current_user_identity((*active_user)->identity()); + } + else { + m_current_user = nullptr; + if (m_metadata_manager) + m_metadata_manager->set_current_user_identity(""); } - - if (m_metadata_manager) - m_metadata_manager->set_current_user_identity(""); - m_current_user = nullptr; } void SyncManager::set_current_user(const std::string& user_id) diff --git a/src/realm/object-store/sync/sync_manager.hpp b/src/realm/object-store/sync/sync_manager.hpp index 8d38ecfc2ff..d496847c9ac 100644 --- a/src/realm/object-store/sync/sync_manager.hpp +++ b/src/realm/object-store/sync/sync_manager.hpp @@ -193,7 +193,7 @@ class SyncManager : public std::enable_shared_from_this { std::shared_ptr get_current_user() const REQUIRES(!m_user_mutex, !m_file_system_mutex); // Log out a given user - void log_out_user(const std::string& user_id) REQUIRES(!m_user_mutex, !m_file_system_mutex); + void log_out_user(const SyncUser& user) REQUIRES(!m_user_mutex, !m_file_system_mutex); // Sets the currently active user. void set_current_user(const std::string& user_id) REQUIRES(!m_user_mutex, !m_file_system_mutex); diff --git a/src/realm/object-store/sync/sync_user.cpp b/src/realm/object-store/sync/sync_user.cpp index 2fe79c3c275..bd254bef22e 100644 --- a/src/realm/object-store/sync/sync_user.cpp +++ b/src/realm/object-store/sync/sync_user.cpp @@ -358,7 +358,7 @@ void SyncUser::log_out() m_sessions.clear(); } - sync_manager_shared->log_out_user(m_identity); + sync_manager_shared->log_out_user(*this); emit_change_to_subscribers(*this); } diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index 95c849ba8f6..6d8c7c2d811 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -344,13 +344,12 @@ TEST_CASE("app: login_with_credentials integration", "[sync][app][user][baas]") int subscribe_processed = 0; auto token = app->subscribe([&subscribe_processed](auto& app) { if (!subscribe_processed) { - subscribe_processed++; - REQUIRE(static_cast(app.current_user())); + REQUIRE(app.current_user()); } else { - subscribe_processed++; - REQUIRE(!static_cast(app.current_user())); + REQUIRE_FALSE(app.current_user()); } + subscribe_processed++; }); auto user = log_in(app); From 37177fbe41d9ad84e6a3d63c1b8b5df1accf734e Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 2 Aug 2023 09:21:20 -0700 Subject: [PATCH 23/34] Don't perform a large number of writes when opening a sync metadata Realm Initializing sync users read from the metadata Realm used the same code path as initializing newly logged in users, resulting in it performing three no-op write transactions per user to write the data which was just read back to the Realm. --- CHANGELOG.md | 1 + src/realm/object-store/sync/app.cpp | 5 +- src/realm/object-store/sync/sync_manager.cpp | 31 ++---- src/realm/object-store/sync/sync_manager.hpp | 5 +- src/realm/object-store/sync/sync_user.cpp | 100 +++++++------------ src/realm/object-store/sync/sync_user.hpp | 86 ++++++++-------- test/object-store/c_api/c_api.cpp | 3 +- test/object-store/realm.cpp | 3 +- test/object-store/sync/user.cpp | 6 +- 9 files changed, 93 insertions(+), 147 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41bcc3f3b7d..f85fd68e8a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Deleting the server-side user via one of the SyncUsers left the other SyncUsers in an invalid state. - A SyncUser which was originally created via anonymous login and then linked to an identity would still be treated as an anonymous users and removed entirely on logout. (since v10.0.0) +* Reading existing logged-in users on app startup from the sync metadata Realm performed three no-op writes per user on the metadata Realm. ### Breaking changes * SyncUser::provider_type() and realm_user_get_auth_provider() have been removed. Users don't have provider types; identities do. diff --git a/src/realm/object-store/sync/app.cpp b/src/realm/object-store/sync/app.cpp index f5632871e23..fd6cddaf40c 100644 --- a/src/realm/object-store/sync/app.cpp +++ b/src/realm/object-store/sync/app.cpp @@ -595,9 +595,8 @@ void App::get_profile(const std::shared_ptr& sync_user, SyncUserIdentity(get(doc, "id"), get(doc, "provider_type"))); } - sync_user->update_identities(identities); - sync_user->update_user_profile(SyncUserProfile(get(profile_json, "data"))); - sync_user->set_state(SyncUser::State::LoggedIn); + sync_user->update_user_profile(std::move(identities), + SyncUserProfile(get(profile_json, "data"))); self->m_sync_manager->set_current_user(sync_user->identity()); self->emit_change_to_subscribers(*self); } diff --git a/src/realm/object-store/sync/sync_manager.cpp b/src/realm/object-store/sync/sync_manager.cpp index f804e79f948..ed19c22bb8a 100644 --- a/src/realm/object-store/sync/sync_manager.cpp +++ b/src/realm/object-store/sync/sync_manager.cpp @@ -46,16 +46,7 @@ SyncManager::SyncManager() = default; void SyncManager::configure(std::shared_ptr app, const std::string& sync_route, const SyncClientConfig& config) { - struct UserCreationData { - std::string identity; - std::string refresh_token; - std::string access_token; - std::vector identities; - SyncUser::State state; - std::string device_id; - }; - - std::vector users_to_add; + std::vector> users_to_add; { // Locking the mutex here ensures that it is released before locking m_user_mutex util::CheckedLockGuard lock(m_mutex); @@ -113,11 +104,8 @@ void SyncManager::configure(std::shared_ptr app, const std::string& sy auto user_data = users.get(i); auto refresh_token = user_data.refresh_token(); auto access_token = user_data.access_token(); - auto device_id = user_data.device_id(); if (!refresh_token.empty() && !access_token.empty()) { - users_to_add.push_back(UserCreationData{user_data.identity(), std::move(refresh_token), - std::move(access_token), user_data.identities(), - user_data.state(), device_id}); + users_to_add.push_back(std::make_shared(user_data, this)); } } @@ -144,13 +132,7 @@ void SyncManager::configure(std::shared_ptr app, const std::string& sy } { util::CheckedLockGuard lock(m_user_mutex); - for (auto& user_data : users_to_add) { - auto& identity = user_data.identity; - auto user = std::make_shared(user_data.refresh_token, identity, user_data.access_token, - user_data.state, user_data.device_id, this); - user->update_identities(user_data.identities); - m_users.emplace_back(std::move(user)); - } + m_users.insert(m_users.end(), users_to_add.begin(), users_to_add.end()); } } @@ -351,8 +333,8 @@ bool SyncManager::perform_metadata_update(util::FunctionRef SyncManager::get_user(const std::string& user_id, std::string refresh_token, - std::string access_token, std::string device_id) +std::shared_ptr SyncManager::get_user(const std::string& user_id, const std::string& refresh_token, + const std::string& access_token, const std::string& device_id) { util::CheckedLockGuard lock(m_user_mutex); auto it = std::find_if(m_users.begin(), m_users.end(), [&](const auto& user) { @@ -360,8 +342,7 @@ std::shared_ptr SyncManager::get_user(const std::string& user_id, std: }); if (it == m_users.end()) { // No existing user. - auto new_user = std::make_shared(std::move(refresh_token), user_id, std::move(access_token), - SyncUser::State::LoggedIn, device_id, this); + auto new_user = std::make_shared(refresh_token, user_id, access_token, device_id, this); m_users.emplace(m_users.begin(), new_user); { util::CheckedLockGuard lock(m_file_system_mutex); diff --git a/src/realm/object-store/sync/sync_manager.hpp b/src/realm/object-store/sync/sync_manager.hpp index d496847c9ac..19233ddafce 100644 --- a/src/realm/object-store/sync/sync_manager.hpp +++ b/src/realm/object-store/sync/sync_manager.hpp @@ -180,8 +180,9 @@ class SyncManager : public std::enable_shared_from_this { // Get a sync user for a given identity, or create one if none exists yet, and set its token. // If a logged-out user exists, it will marked as logged back in. - std::shared_ptr get_user(const std::string& id, std::string refresh_token, std::string access_token, - std::string device_id) REQUIRES(!m_user_mutex, !m_file_system_mutex); + std::shared_ptr get_user(const std::string& user_id, const std::string& refresh_token, + const std::string& access_token, const std::string& device_id) + REQUIRES(!m_user_mutex, !m_file_system_mutex); // Get an existing user for a given identifier, if one exists and is logged in. std::shared_ptr get_existing_logged_in_user(const std::string& user_id) const REQUIRES(!m_user_mutex); diff --git a/src/realm/object-store/sync/sync_user.cpp b/src/realm/object-store/sync/sync_user.cpp index bd254bef22e..1b235a4cb31 100644 --- a/src/realm/object-store/sync/sync_user.cpp +++ b/src/realm/object-store/sync/sync_user.cpp @@ -84,17 +84,17 @@ SyncUserIdentity::SyncUserIdentity(const std::string& id, const std::string& pro SyncUserContextFactory SyncUser::s_binding_context_factory; std::mutex SyncUser::s_binding_context_factory_mutex; -SyncUser::SyncUser(std::string refresh_token, std::string identity, std::string access_token, SyncUser::State state, - std::string device_id, SyncManager* sync_manager) - : m_state(state) - , m_identity(std::move(identity)) - , m_refresh_token(RealmJWT(std::move(refresh_token))) - , m_access_token(RealmJWT(std::move(access_token))) - , m_device_id(std::move(device_id)) +SyncUser::SyncUser(const std::string& refresh_token, const std::string& id, const std::string& access_token, + const std::string& device_id, SyncManager* sync_manager) + : m_state(State::LoggedIn) + , m_identity(id) + , m_refresh_token(RealmJWT(refresh_token)) + , m_access_token(RealmJWT(access_token)) + , m_device_id(device_id) , m_sync_manager(sync_manager) { { - std::lock_guard lock(s_binding_context_factory_mutex); + std::lock_guard lock(s_binding_context_factory_mutex); if (s_binding_context_factory) { m_binding_context = s_binding_context_factory(); } @@ -102,13 +102,32 @@ SyncUser::SyncUser(std::string refresh_token, std::string identity, std::string m_sync_manager->perform_metadata_update([&](const auto& manager) NO_THREAD_SAFETY_ANALYSIS { auto metadata = manager.get_or_make_user_metadata(m_identity); - metadata->set_state_and_tokens(state, m_access_token.token, m_refresh_token.token); + metadata->set_state_and_tokens(State::LoggedIn, m_access_token.token, m_refresh_token.token); metadata->set_device_id(m_device_id); m_legacy_identities = metadata->legacy_identities(); this->m_user_profile = metadata->profile(); }); } +SyncUser::SyncUser(const SyncUserMetadata& data, SyncManager* sync_manager) + : m_state(data.state()) + , m_legacy_identities(data.legacy_identities()) + , m_identity(data.identity()) + , m_refresh_token(RealmJWT(data.refresh_token())) + , m_access_token(RealmJWT(data.access_token())) + , m_user_identities(data.identities()) + , m_user_profile(data.profile()) + , m_device_id(data.device_id()) + , m_sync_manager(sync_manager) +{ + { + std::lock_guard lock(s_binding_context_factory_mutex); + if (s_binding_context_factory) { + m_binding_context = s_binding_context_factory(); + } + } +} + std::shared_ptr SyncUser::sync_manager() const { util::CheckedLockGuard lk(m_mutex); @@ -218,41 +237,6 @@ std::vector> SyncUser::revive_sessions() return sessions_to_revive; } -void SyncUser::update_refresh_token(std::string&& token) -{ - std::vector> sessions_to_revive; - { - util::CheckedLockGuard lock(m_mutex); - util::CheckedLockGuard lock2(m_tokens_mutex); - switch (m_state) { - case State::Removed: - return; - case State::LoggedIn: - m_refresh_token = RealmJWT(std::move(token)); - break; - case State::LoggedOut: { - m_refresh_token = RealmJWT(std::move(token)); - m_state = State::LoggedIn; - sessions_to_revive = revive_sessions(); - break; - } - } - - m_sync_manager->perform_metadata_update([&, raw_refresh_token = m_refresh_token.token](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(m_identity); - metadata->set_refresh_token(raw_refresh_token); - }); - } - // (Re)activate all pending sessions. - // Note that we do this after releasing the lock, since the session may - // need to access protected User state in the process of binding itself. - for (auto& session : sessions_to_revive) { - session->revive_if_needed(); - } - - emit_change_to_subscribers(*this); -} - void SyncUser::update_access_token(std::string&& token) { std::vector> sessions_to_revive; @@ -295,22 +279,6 @@ std::vector SyncUser::identities() const return m_user_identities; } - -void SyncUser::update_identities(std::vector identities) -{ - util::CheckedLockGuard lock(m_mutex); - if (m_state != SyncUser::State::LoggedIn) { - return; - } - - m_user_identities = identities; - - m_sync_manager->perform_metadata_update([&](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(m_identity); - metadata->set_identities(identities); - }); -} - void SyncUser::log_out() { // We'll extend the lifetime of SyncManager while holding m_mutex so that we know it's safe to call methods on it @@ -446,18 +414,20 @@ util::Optional SyncUser::custom_data() const return m_access_token.user_data; } -void SyncUser::update_user_profile(const SyncUserProfile& profile) +void SyncUser::update_user_profile(std::vector identities, SyncUserProfile profile) { util::CheckedLockGuard lock(m_mutex); - if (m_state != SyncUser::State::LoggedIn) { + if (m_state == SyncUser::State::Removed) { return; } - m_user_profile = profile; + m_user_identities = std::move(identities); + m_user_profile = std::move(profile); - m_sync_manager->perform_metadata_update([&](const auto& manager) { + m_sync_manager->perform_metadata_update([&](const auto& manager) NO_THREAD_SAFETY_ANALYSIS { auto metadata = manager.get_or_make_user_metadata(m_identity); - metadata->set_user_profile(profile); + metadata->set_identities(m_user_identities); + metadata->set_user_profile(m_user_profile); }); } diff --git a/src/realm/object-store/sync/sync_user.hpp b/src/realm/object-store/sync/sync_user.hpp index 12b0cab083b..9af11bf2ac6 100644 --- a/src/realm/object-store/sync/sync_user.hpp +++ b/src/realm/object-store/sync/sync_user.hpp @@ -38,8 +38,9 @@ namespace app { struct AppError; class MongoClient; } // namespace app -class SyncSession; class SyncManager; +class SyncSession; +class SyncUserMetadata; // A superclass that bindings can inherit from in order to store information // upon a `SyncUser` object. @@ -179,10 +180,6 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba Removed, }; - // Don't use this directly; use the `SyncManager` APIs. Public for use with `make_shared`. - SyncUser(std::string refresh_token, std::string id, std::string access_token, SyncUser::State state, - std::string device_id, SyncManager* sync_manager); - // Return a list of all sessions belonging to this user. std::vector> all_sessions() REQUIRES(!m_mutex); @@ -192,28 +189,6 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba // for testing purposes, and for bindings for consumers that are servers or tools. std::shared_ptr session_for_on_disk_path(const std::string& path) REQUIRES(!m_mutex); - // Update the user's state and refresh/access tokens atomically in a Realm transaction. - // If the user is transitioning between LoggedIn and LoggedOut, then the access_token and - // refresh token must be empty, and likewise must not be empty if transitioning between - // logged out and logged in. - // Note that this is called by the SyncManager, and should not be directly called. - void update_state_and_tokens(SyncUser::State state, const std::string& access_token, - const std::string& refresh_token) REQUIRES(!m_mutex, !m_tokens_mutex); - - // Update the user's refresh token. If the user is logged out, it will log itself back in. - // Note that this is called by the SyncManager, and should not be directly called. - void update_refresh_token(std::string&& token) REQUIRES(!m_mutex, !m_tokens_mutex); - - // Update the user's access token. If the user is logged out, it will log itself back in. - // Note that this is called by the SyncManager, and should not be directly called. - void update_access_token(std::string&& token) REQUIRES(!m_mutex, !m_tokens_mutex); - - // Update the user's profile. - void update_user_profile(const SyncUserProfile& profile) REQUIRES(!m_mutex); - - // Update the user's identities. - void update_identities(std::vector identities) REQUIRES(!m_mutex); - // Log the user out and mark it as such. This will also close its associated Sessions. void log_out() REQUIRES(!m_mutex, !m_tokens_mutex); @@ -234,28 +209,57 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba } std::string access_token() const REQUIRES(!m_tokens_mutex); - std::string refresh_token() const REQUIRES(!m_tokens_mutex); - std::string device_id() const REQUIRES(!m_mutex); - bool has_device_id() const REQUIRES(!m_mutex); - SyncUserProfile user_profile() const REQUIRES(!m_mutex); - std::vector identities() const REQUIRES(!m_mutex); + State state() const REQUIRES(!m_mutex); // Custom user data embedded in the access token. util::Optional custom_data() const REQUIRES(!m_tokens_mutex); - State state() const REQUIRES(!m_mutex); - void set_state(SyncUser::State state) REQUIRES(!m_mutex); - std::shared_ptr binding_context() const { return m_binding_context.load(); } + // Optionally set a context factory. If so, must be set before any sessions are created. + static void set_binding_context_factory(SyncUserContextFactory factory); + + std::shared_ptr sync_manager() const REQUIRES(!m_mutex); + + /// Retrieves a general-purpose service client for the Realm Cloud service + /// @param service_name The name of the cluster + app::MongoClient mongo_client(const std::string& service_name) REQUIRES(!m_mutex); + + // ------------------------------------------------------------------------ + // All of the following are called by `SyncManager` and are public only for + // testing purposes. SDKs should not call these directly in non-test code + // or expose them in the public API. + + // Don't use this directly; use the `SyncManager` APIs. Public for use with `make_shared`. + SyncUser(const std::string& refresh_token, const std::string& id, const std::string& access_token, + const std::string& device_id, SyncManager* sync_manager); + SyncUser(const SyncUserMetadata& data, SyncManager* sync_manager); + + void set_state(SyncUser::State state) REQUIRES(!m_mutex); + + // Update the user's state and refresh/access tokens atomically in a Realm transaction. + // If the user is transitioning between LoggedIn and LoggedOut, then the access_token and + // refresh token must be empty, and likewise must not be empty if transitioning between + // logged out and logged in. + // Note that this is called by the SyncManager, and should not be directly called. + void update_state_and_tokens(SyncUser::State state, const std::string& access_token, + const std::string& refresh_token) REQUIRES(!m_mutex, !m_tokens_mutex); + + // Update the user's access token. If the user is logged out, it will log itself back in. + // Note that this is called by the SyncManager, and should not be directly called. + void update_access_token(std::string&& token) REQUIRES(!m_mutex, !m_tokens_mutex); + + // Update the user's profile and identities. + void update_user_profile(std::vector identities, SyncUserProfile profile) REQUIRES(!m_mutex); + // Register a session to this user. // A registered session will be bound at the earliest opportunity: either // immediately, or upon the user becoming Active. @@ -263,7 +267,7 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba void register_session(std::shared_ptr) REQUIRES(!m_mutex); /// Refreshes the custom data for this user - /// If update_location is true, the location metadata will be queried before the request + /// If `update_location` is true, the location metadata will be queried before the request void refresh_custom_data(bool update_location, util::UniqueFunction)> completion_block) REQUIRES(!m_mutex); @@ -274,15 +278,7 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba /// true. bool access_token_refresh_required() const REQUIRES(!m_mutex, !m_tokens_mutex); - // Optionally set a context factory. If so, must be set before any sessions are created. - static void set_binding_context_factory(SyncUserContextFactory factory); - - std::shared_ptr sync_manager() const REQUIRES(!m_mutex); - - /// Retrieves a general-purpose service client for the Realm Cloud service - /// @param service_name The name of the cluster - app::MongoClient mongo_client(const std::string& service_name) REQUIRES(!m_mutex); - + // Hook for testing access token timeouts void set_seconds_to_adjust_time_for_testing(int seconds) { m_seconds_to_adjust_time_for_testing.store(seconds); diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 0d07d18db16..2c6637d851f 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -5006,8 +5006,7 @@ TEST_CASE("C API - async_open", "[sync][pbs][c_api]") { realm_sync_config_t* sync_config = realm_sync_config_new(&user, "realm"); realm_sync_config_set_initial_subscription_handler(sync_config, task_init_subscription, false, nullptr, nullptr); - sync_config->user->update_refresh_token(std::string(invalid_token)); - sync_config->user->update_access_token(std::move(invalid_token)); + sync_config->user->update_state_and_tokens(SyncUser::State::LoggedIn, invalid_token, invalid_token); realm_config_set_path(config, test_config.path.c_str()); realm_config_set_schema_version(config, 1); diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 1001eb0c7e5..666955ecb8a 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1184,8 +1184,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { TestSyncManager tsm(tsm_config); SyncTestFile config(tsm.app(), "realm"); - config.sync_config->user->update_refresh_token(std::string(invalid_token)); - config.sync_config->user->update_access_token(std::move(invalid_token)); + config.sync_config->user->update_state_and_tokens(SyncUser::State::LoggedIn, invalid_token, invalid_token); bool got_error = false; config.sync_config->error_handler = [&](std::shared_ptr, SyncError) { diff --git a/test/object-store/sync/user.cpp b/test/object-store/sync/user.cpp index 457b2873028..51a046cdb27 100644 --- a/test/object-store/sync/user.cpp +++ b/test/object-store/sync/user.cpp @@ -174,7 +174,7 @@ TEST_CASE("sync_user: user persistence", "[sync][user]") { const std::string access_token = ENCODE_FAKE_JWT("a-token-1"); const std::vector identities{{"12345", "test_case_provider"}}; auto user = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); - user->update_identities(identities); + user->update_user_profile(identities, {}); // Now try to pull the user out of the shadow manager directly. auto metadata = manager.get_or_make_user_metadata(identity, false); REQUIRE((bool)metadata); @@ -191,7 +191,7 @@ TEST_CASE("sync_user: user persistence", "[sync][user]") { const std::string access_token = ENCODE_FAKE_JWT("a-token-1"); const std::vector identities{{"12345", "test_case_provider"}}; auto user = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); - user->update_identities(identities); + user->update_user_profile(identities, {}); user->log_out(); // Now try to pull the user out of the shadow manager directly. auto metadata = manager.get_or_make_user_metadata(identity, false); @@ -242,7 +242,7 @@ TEST_CASE("sync_user: user persistence", "[sync][user]") { const std::string access_token = ENCODE_FAKE_JWT("a-token-3"); // Create the user and validate it. auto user = sync_manager->get_user(identity, refresh_token, access_token, dummy_device_id); - user->update_identities({{"id", app::IdentityProviderAnonymous}}); + user->update_user_profile({{"id", app::IdentityProviderAnonymous}}, {}); auto marked_users = manager.all_users_marked_for_removal(); REQUIRE(marked_users.size() == 0); // Log out the user. From a365369442e47712bf831939401ba0c31c6bbe93 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 2 Aug 2023 09:34:40 -0700 Subject: [PATCH 24/34] Remove SyncUser::operator==() It had incorrect semantics (users from different Apps which happened to have the same id would compare equal), and simply isn't neccesary as there should only ever be a single SyncUser instance per user and so pointer equality suffices. --- CHANGELOG.md | 1 + src/realm/object-store/c_api/types.hpp | 2 +- src/realm/object-store/impl/realm_coordinator.cpp | 2 +- src/realm/object-store/sync/sync_user.hpp | 11 ----------- 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f85fd68e8a7..1205d179189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ### Breaking changes * SyncUser::provider_type() and realm_user_get_auth_provider() have been removed. Users don't have provider types; identities do. * SyncUser no longer has a `local_identity()`. `identity()` has been guaranteed to be unique per App ever since v10. +* SyncUser no longer overrides operator==. Pointer equality should be used to compare sync users. ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. diff --git a/src/realm/object-store/c_api/types.hpp b/src/realm/object-store/c_api/types.hpp index 13b8f3ac18d..d12d7625296 100644 --- a/src/realm/object-store/c_api/types.hpp +++ b/src/realm/object-store/c_api/types.hpp @@ -661,7 +661,7 @@ struct realm_user : realm::c_api::WrapC, std::shared_ptr { bool equals(const WrapC& other) const noexcept final { if (auto ptr = dynamic_cast(&other)) { - return *get() == *(ptr->get()); + return get() == ptr->get(); } return false; } diff --git a/src/realm/object-store/impl/realm_coordinator.cpp b/src/realm/object-store/impl/realm_coordinator.cpp index f5a8e13c217..6cfa3858d2e 100644 --- a/src/realm/object-store/impl/realm_coordinator.cpp +++ b/src/realm/object-store/impl/realm_coordinator.cpp @@ -183,7 +183,7 @@ void RealmCoordinator::set_config(const Realm::Config& config) if (config.sync_config) { auto old_user = m_config.sync_config->user; auto new_user = config.sync_config->user; - if (old_user && new_user && *old_user != *new_user) { + if (old_user != new_user) { throw LogicError( ErrorCodes::MismatchedConfig, util::format("Realm at path '%1' already opened with different sync user.", config.path)); diff --git a/src/realm/object-store/sync/sync_user.hpp b/src/realm/object-store/sync/sync_user.hpp index 9af11bf2ac6..994607bec44 100644 --- a/src/realm/object-store/sync/sync_user.hpp +++ b/src/realm/object-store/sync/sync_user.hpp @@ -284,17 +284,6 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba m_seconds_to_adjust_time_for_testing.store(seconds); } - /// Check the SyncUsers passed as argument have the same remote identity id. - friend bool operator==(const SyncUser& lhs, const SyncUser& rhs) - { - return lhs.identity() == rhs.identity(); - } - - friend bool operator!=(const SyncUser& lhs, const SyncUser& rhs) - { - return !(lhs == rhs); - } - protected: friend class SyncManager; void detach_from_sync_manager() REQUIRES(!m_mutex); From 64bf8424d27b54de776b046cd936a751c3a133b6 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 2 Aug 2023 09:41:16 -0700 Subject: [PATCH 25/34] Adjust SyncUser's internal API to no longer permit invalid states SyncUser previously allowed setting the state to LoggedIn without setting its tokens, and conversely allowed setting tokens while not logged in. This was error-prone and happened to result in a lock-order inversion due to that both the state and the tokens had to be checked in places where we only wanted to care about one of them. --- CHANGELOG.md | 11 +- src/realm/object-store/sync/sync_manager.cpp | 8 +- src/realm/object-store/sync/sync_user.cpp | 118 ++++++++----------- src/realm/object-store/sync/sync_user.hpp | 21 ++-- test/object-store/c_api/c_api.cpp | 2 +- test/object-store/realm.cpp | 2 +- test/object-store/sync/app.cpp | 36 ++++-- test/object-store/sync/user.cpp | 8 +- 8 files changed, 92 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1205d179189..2d80254703d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,14 @@ - Removing one of the SyncUsers would delete all local Realm files for all SyncUsers for that user. - Deleting the server-side user via one of the SyncUsers left the other SyncUsers in an invalid state. - A SyncUser which was originally created via anonymous login and then linked to an identity would still be treated as an anonymous users and removed entirely on logout. - (since v10.0.0) -* Reading existing logged-in users on app startup from the sync metadata Realm performed three no-op writes per user on the metadata Realm. + ([PR #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0) +* Reading existing logged-in users on app startup from the sync metadata Realm performed three no-op writes per user on the metadata Realm ([PR #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0). +* If a user was logged out while an access token refresh was in progress, the refresh completing would mark the user as logged in again and the user would be in an inconsistent state ([PR #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0). ### Breaking changes -* SyncUser::provider_type() and realm_user_get_auth_provider() have been removed. Users don't have provider types; identities do. -* SyncUser no longer has a `local_identity()`. `identity()` has been guaranteed to be unique per App ever since v10. -* SyncUser no longer overrides operator==. Pointer equality should be used to compare sync users. +* SyncUser::provider_type() and realm_user_get_auth_provider() have been removed. Users don't have provider types; identities do. `SyncUser::is_anonymous()` is a more correct version of checking if the provider type is anonymous ([PR #6837](https://github.com/realm/realm-core/pull/6837)). +* SyncUser no longer has a `local_identity()`. `identity()` has been guaranteed to be unique per App ever since v10 ([PR #6837](https://github.com/realm/realm-core/pull/6837)). +* SyncUser no longer overrides operator==. Pointer equality should be used to compare sync users ([PR #6837](https://github.com/realm/realm-core/pull/6837)). ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. diff --git a/src/realm/object-store/sync/sync_manager.cpp b/src/realm/object-store/sync/sync_manager.cpp index ed19c22bb8a..eba75289246 100644 --- a/src/realm/object-store/sync/sync_manager.cpp +++ b/src/realm/object-store/sync/sync_manager.cpp @@ -355,7 +355,7 @@ std::shared_ptr SyncManager::get_user(const std::string& user_id, cons else { // LoggedOut => LoggedIn auto user = *it; REALM_ASSERT(user->state() != SyncUser::State::Removed); - user->update_state_and_tokens(SyncUser::State::LoggedIn, std::move(access_token), std::move(refresh_token)); + user->log_in(access_token, refresh_token); return user; } } @@ -443,10 +443,8 @@ void SyncManager::set_current_user(const std::string& user_id) void SyncManager::remove_user(const std::string& user_id) { util::CheckedLockGuard lock(m_user_mutex); - auto user = get_user_for_identity(user_id); - if (!user) - return; - user->set_state(SyncUser::State::Removed); + if (auto user = get_user_for_identity(user_id)) + user->invalidate(); } void SyncManager::delete_user(const std::string& user_id) diff --git a/src/realm/object-store/sync/sync_user.cpp b/src/realm/object-store/sync/sync_user.cpp index 1b235a4cb31..1f113287856 100644 --- a/src/realm/object-store/sync/sync_user.cpp +++ b/src/realm/object-store/sync/sync_user.cpp @@ -93,6 +93,7 @@ SyncUser::SyncUser(const std::string& refresh_token, const std::string& id, cons , m_device_id(device_id) , m_sync_manager(sync_manager) { + REALM_ASSERT(!access_token.empty() && !refresh_token.empty()); { std::lock_guard lock(s_binding_context_factory_mutex); if (s_binding_context_factory) { @@ -120,6 +121,15 @@ SyncUser::SyncUser(const SyncUserMetadata& data, SyncManager* sync_manager) , m_device_id(data.device_id()) , m_sync_manager(sync_manager) { + // Check for inconsistent state in the metadata Realm. This shouldn't happen, + // but previous versions could sometimes mark a user as logged in with an + // empty refresh token. + if (m_state == State::LoggedIn && (m_refresh_token.token.empty() || m_access_token.token.empty())) { + m_state = State::LoggedOut; + m_refresh_token = {}; + m_access_token = {}; + } + { std::lock_guard lock(s_binding_context_factory_mutex); if (s_binding_context_factory) { @@ -132,8 +142,10 @@ std::shared_ptr SyncUser::sync_manager() const { util::CheckedLockGuard lk(m_mutex); if (m_state == State::Removed) { - throw std::logic_error(util::format( - "Cannot start a sync session for user '%1' because this user has been removed.", identity())); + throw app::AppError( + ErrorCodes::ClientUserNotFound, + util::format("Cannot start a sync session for user '%1' because this user has been removed.", + m_identity)); } REALM_ASSERT(m_sync_manager); return m_sync_manager->shared_from_this(); @@ -184,33 +196,22 @@ std::shared_ptr SyncUser::session_for_on_disk_path(const std::strin return locked; } -void SyncUser::update_state_and_tokens(SyncUser::State state, const std::string& access_token, - const std::string& refresh_token) +void SyncUser::log_in(const std::string& access_token, const std::string& refresh_token) { + REALM_ASSERT(!access_token.empty()); + REALM_ASSERT(!refresh_token.empty()); std::vector> sessions_to_revive; { util::CheckedLockGuard lock1(m_mutex); util::CheckedLockGuard lock2(m_tokens_mutex); - m_state = state; - m_access_token = access_token.empty() ? RealmJWT{} : RealmJWT(access_token); - m_refresh_token = refresh_token.empty() ? RealmJWT{} : RealmJWT(refresh_token); - switch (m_state) { - case State::Removed: - // Call set_state() rather than update_state_and_tokens to remove a user. - REALM_UNREACHABLE(); - case State::LoggedIn: - sessions_to_revive = revive_sessions(); - break; - case State::LoggedOut: { - REALM_ASSERT(m_access_token == RealmJWT{}); - REALM_ASSERT(m_refresh_token == RealmJWT{}); - break; - } - } + m_state = State::LoggedIn; + m_access_token = RealmJWT(access_token); + m_refresh_token = RealmJWT(refresh_token); + sessions_to_revive = revive_sessions(); m_sync_manager->perform_metadata_update([&](const auto& manager) { auto metadata = manager.get_or_make_user_metadata(m_identity); - metadata->set_state_and_tokens(state, access_token, refresh_token); + metadata->set_state_and_tokens(State::LoggedIn, access_token, refresh_token); }); } // (Re)activate all pending sessions. @@ -223,6 +224,23 @@ void SyncUser::update_state_and_tokens(SyncUser::State state, const std::string& emit_change_to_subscribers(*this); } +void SyncUser::invalidate() +{ + { + util::CheckedLockGuard lock1(m_mutex); + util::CheckedLockGuard lock2(m_tokens_mutex); + m_state = State::Removed; + m_access_token = {}; + m_refresh_token = {}; + + m_sync_manager->perform_metadata_update([&](const auto& manager) { + auto metadata = manager.get_or_make_user_metadata(m_identity); + metadata->set_state_and_tokens(State::Removed, "", ""); + }); + } + emit_change_to_subscribers(*this); +} + std::vector> SyncUser::revive_sessions() { std::vector> sessions_to_revive; @@ -239,37 +257,19 @@ std::vector> SyncUser::revive_sessions() void SyncUser::update_access_token(std::string&& token) { - std::vector> sessions_to_revive; { util::CheckedLockGuard lock(m_mutex); - util::CheckedLockGuard lock2(m_tokens_mutex); - switch (m_state) { - case State::Removed: - return; - case State::LoggedIn: - m_access_token = RealmJWT(std::move(token)); - break; - case State::LoggedOut: { - m_access_token = RealmJWT(std::move(token)); - m_state = State::LoggedIn; - sessions_to_revive = revive_sessions(); - break; - } - } + if (m_state != State::LoggedIn) + return; + util::CheckedLockGuard lock2(m_tokens_mutex); + m_access_token = RealmJWT(std::move(token)); m_sync_manager->perform_metadata_update([&, raw_access_token = m_access_token.token](const auto& manager) { auto metadata = manager.get_or_make_user_metadata(m_identity); metadata->set_access_token(raw_access_token); }); } - // (Re)activate all pending sessions. - // Note that we do this after releasing the lock, since the session may - // need to access protected User state in the process of binding itself. - for (auto& session : sessions_to_revive) { - session->revive_if_needed(); - } - emit_change_to_subscribers(*this); } @@ -333,13 +333,7 @@ void SyncUser::log_out() bool SyncUser::is_logged_in() const { util::CheckedLockGuard lock(m_mutex); - util::CheckedLockGuard lock2(m_tokens_mutex); - return do_is_logged_in(); -} - -bool SyncUser::do_is_logged_in() const -{ - return !m_access_token.token.empty() && !m_refresh_token.token.empty() && m_state == State::LoggedIn; + return m_state == State::LoggedIn; } bool SyncUser::is_anonymous() const @@ -351,15 +345,10 @@ bool SyncUser::is_anonymous() const bool SyncUser::do_is_anonymous() const { - return do_is_logged_in() && m_user_identities.size() == 1 && + return m_state == State::LoggedIn && m_user_identities.size() == 1 && m_user_identities[0].provider_type == app::IdentityProviderAnonymous; } -void SyncUser::invalidate() -{ - set_state(SyncUser::State::Removed); -} - std::string SyncUser::refresh_token() const { util::CheckedLockGuard lock(m_tokens_mutex); @@ -390,18 +379,6 @@ SyncUser::State SyncUser::state() const return m_state; } -void SyncUser::set_state(SyncUser::State state) -{ - util::CheckedLockGuard lock(m_mutex); - m_state = state; - - REALM_ASSERT(m_sync_manager); - m_sync_manager->perform_metadata_update([&](const auto& manager) { - auto metadata = manager.get_or_make_user_metadata(m_identity); - metadata->set_state(state); - }); -} - SyncUserProfile SyncUser::user_profile() const { util::CheckedLockGuard lock(m_mutex); @@ -506,12 +483,11 @@ bool SyncUser::access_token_refresh_required() const { using namespace std::chrono; constexpr size_t buffer_seconds = 5; // arbitrary - util::CheckedLockGuard lock(m_mutex); - util::CheckedLockGuard lock2(m_tokens_mutex); + util::CheckedLockGuard lock(m_tokens_mutex); const auto now = duration_cast(system_clock::now().time_since_epoch()).count() + m_seconds_to_adjust_time_for_testing.load(std::memory_order_relaxed); const auto threshold = now - buffer_seconds; - return do_is_logged_in() && m_access_token.expires_at < static_cast(threshold); + return !m_access_token.token.empty() && m_access_token.expires_at < static_cast(threshold); } } // namespace realm diff --git a/src/realm/object-store/sync/sync_user.hpp b/src/realm/object-store/sync/sync_user.hpp index 994607bec44..3f41cc77d99 100644 --- a/src/realm/object-store/sync/sync_user.hpp +++ b/src/realm/object-store/sync/sync_user.hpp @@ -243,15 +243,12 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba const std::string& device_id, SyncManager* sync_manager); SyncUser(const SyncUserMetadata& data, SyncManager* sync_manager); - void set_state(SyncUser::State state) REQUIRES(!m_mutex); + // Atomically set the user to be logged in and update both tokens. + void log_in(const std::string& access_token, const std::string& refresh_token) + REQUIRES(!m_mutex, !m_tokens_mutex); - // Update the user's state and refresh/access tokens atomically in a Realm transaction. - // If the user is transitioning between LoggedIn and LoggedOut, then the access_token and - // refresh token must be empty, and likewise must not be empty if transitioning between - // logged out and logged in. - // Note that this is called by the SyncManager, and should not be directly called. - void update_state_and_tokens(SyncUser::State state, const std::string& access_token, - const std::string& refresh_token) REQUIRES(!m_mutex, !m_tokens_mutex); + // Atomically set the user to be removed and remove tokens. + void invalidate() REQUIRES(!m_mutex, !m_tokens_mutex); // Update the user's access token. If the user is logged out, it will log itself back in. // Note that this is called by the SyncManager, and should not be directly called. @@ -276,7 +273,7 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba /// Checks the expiry on the access token against the local time and if it is invalid or expires soon, returns /// true. - bool access_token_refresh_required() const REQUIRES(!m_mutex, !m_tokens_mutex); + bool access_token_refresh_required() const REQUIRES(!m_tokens_mutex); // Hook for testing access token timeouts void set_seconds_to_adjust_time_for_testing(int seconds) @@ -292,8 +289,7 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba static SyncUserContextFactory s_binding_context_factory; static std::mutex s_binding_context_factory_mutex; - bool do_is_logged_in() const REQUIRES(m_tokens_mutex, m_mutex); - bool do_is_anonymous() const REQUIRES(m_tokens_mutex, m_mutex); + bool do_is_anonymous() const REQUIRES(m_mutex); std::vector> revive_sessions() REQUIRES(m_mutex); @@ -305,9 +301,6 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba // used to locate existing files. std::vector m_legacy_identities; - // Mark the user as invalid, since a fatal user-related error was encountered. - void invalidate() REQUIRES(!m_mutex); - mutable util::CheckedMutex m_mutex; // Set by the server. The unique ID of the user account on the Realm Application. diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 2c6637d851f..481dec645e6 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -5006,7 +5006,7 @@ TEST_CASE("C API - async_open", "[sync][pbs][c_api]") { realm_sync_config_t* sync_config = realm_sync_config_new(&user, "realm"); realm_sync_config_set_initial_subscription_handler(sync_config, task_init_subscription, false, nullptr, nullptr); - sync_config->user->update_state_and_tokens(SyncUser::State::LoggedIn, invalid_token, invalid_token); + sync_config->user->log_in(invalid_token, invalid_token); realm_config_set_path(config, test_config.path.c_str()); realm_config_set_schema_version(config, 1); diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 666955ecb8a..4cd47753832 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1184,7 +1184,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { TestSyncManager tsm(tsm_config); SyncTestFile config(tsm.app(), "realm"); - config.sync_config->user->update_state_and_tokens(SyncUser::State::LoggedIn, invalid_token, invalid_token); + config.sync_config->user->log_in(invalid_token, invalid_token); bool got_error = false; config.sync_config->error_handler = [&](std::shared_ptr, SyncError) { diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index 6d8c7c2d811..5251e180bda 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -2868,8 +2868,8 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { std::shared_ptr user = app->current_user(); REQUIRE(user); REQUIRE(!user->access_token_refresh_required()); - // Make the SyncUser behave as if the client clock is 31 minutes fast, so the token looks expired locallaly - // (access tokens have an lifetime of 30 mintutes today). + // Make the SyncUser behave as if the client clock is 31 minutes fast, so the token looks expired locally + // (access tokens have an lifetime of 30 minutes today). user->set_seconds_to_adjust_time_for_testing(31 * 60); REQUIRE(user->access_token_refresh_required()); @@ -2982,6 +2982,17 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { REQUIRE(!user->is_logged_in()); } + SECTION("User is left logged out if logged out while the refresh is in progress") { + REQUIRE(user->is_logged_in()); + transport->request_hook = [&](const Request&) { + user->log_out(); + }; + SyncTestFile config(app, partition, schema); + auto r = Realm::get_shared_realm(config); + REQUIRE_FALSE(user->is_logged_in()); + REQUIRE(user->state() == SyncUser::State::LoggedOut); + } + SECTION("Requests that receive an error are retried on a backoff") { using namespace std::chrono; std::vector> response_times; @@ -3193,11 +3204,10 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { anon_user->identity())); }); - REQUIRE_THROWS_MATCHES( - Realm::get_shared_realm(config), std::logic_error, - Catch::Matchers::Message( - util::format("Cannot start a sync session for user '%1' because this user has been removed.", - anon_user->identity()))); + REQUIRE_EXCEPTION( + Realm::get_shared_realm(config), ClientUserNotFound, + util::format("Cannot start a sync session for user '%1' because this user has been removed.", + anon_user->identity())); } SECTION("Opening a Realm with a removed email user results produces an exception") { @@ -3216,11 +3226,11 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { REQUIRE_FALSE(email_user->is_logged_in()); REQUIRE(email_user->state() == SyncUser::State::Removed); - // should not be able to open a sync'd Realm with an invalid user - REQUIRE_THROWS_MATCHES( - Realm::get_shared_realm(config), std::logic_error, - Catch::Matchers::Message(util::format( - "Cannot start a sync session for user '%1' because this user has been removed.", user_ident))); + // should not be able to open a synced Realm with an invalid user + REQUIRE_EXCEPTION( + Realm::get_shared_realm(config), ClientUserNotFound, + util::format("Cannot start a sync session for user '%1' because this user has been removed.", + user_ident)); std::shared_ptr new_user_instance = log_in(app, creds); // the previous instance is still invalid @@ -4891,7 +4901,7 @@ TEST_CASE("app: app destroyed during token refresh", "[sync][app][user][token]") // Ignore these errors, since there's not really an app out there... // Primarily make sure we don't crash unexpectedly std::vector expected_errors = {"Bad WebSocket", "Connection Failed", "user has been removed", - "Connection refused"}; + "Connection refused", "The user is not logged in"}; auto expected = std::find_if(expected_errors.begin(), expected_errors.end(), [error](const char* err_msg) { return error.status.reason().find(err_msg) != std::string::npos; diff --git a/test/object-store/sync/user.cpp b/test/object-store/sync/user.cpp index 51a046cdb27..f4643789bdd 100644 --- a/test/object-store/sync/user.cpp +++ b/test/object-store/sync/user.cpp @@ -97,19 +97,19 @@ TEST_CASE("sync_user: update state and tokens", "[sync][user]") { REQUIRE(user->is_logged_in()); REQUIRE(user->refresh_token() == refresh_token); - user->update_state_and_tokens(SyncUser::State::LoggedIn, second_access_token, second_refresh_token); + user->log_in(second_access_token, second_refresh_token); REQUIRE(user->is_logged_in()); REQUIRE(user->refresh_token() == second_refresh_token); - user->update_state_and_tokens(SyncUser::State::LoggedOut, "", ""); + user->log_out(); REQUIRE(!user->is_logged_in()); REQUIRE(user->refresh_token().empty()); - user->update_state_and_tokens(SyncUser::State::LoggedIn, access_token, refresh_token); + user->log_in(access_token, refresh_token); REQUIRE(user->is_logged_in()); REQUIRE(user->refresh_token() == refresh_token); - sync_manager->remove_user(identity); + user->invalidate(); } TEST_CASE("sync_user: SyncManager `get_existing_logged_in_user()` API", "[sync][user]") { From eb34d4cb210b5e5374717c512ea910e3cb7d0bba Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Wed, 20 Sep 2023 13:49:30 +0200 Subject: [PATCH 26/34] Add Android.bp blueprints (#6985) * add blueprints for aosp build * extract shared libraries we depend on * whitespace --------- Co-authored-by: Yavor Georgiev --- Android.bp | 194 ++++++++++++++++++++ src/external/json/LICENSE.MIT | 21 +++ src/realm/sync/network/network_ssl.cpp | 2 +- tools/generate-version-numbers-for-soong.sh | 14 ++ 4 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 Android.bp create mode 100644 src/external/json/LICENSE.MIT create mode 100755 tools/generate-version-numbers-for-soong.sh diff --git a/Android.bp b/Android.bp new file mode 100644 index 00000000000..e214f35ef9e --- /dev/null +++ b/Android.bp @@ -0,0 +1,194 @@ +package { + default_applicable_licenses: [ + "external_realm_license", + "external_realm_dep_intel_math_library_license", + "external_realm_dep_mpark_variant_license", + "external_realm_dep_nlohmann_json_license", + "external_realm_dep_s2_license", + ], +} + +license { + name: "external_realm_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "LICENSE", + ], +} + +license { + name: "external_realm_dep_intel_math_library_license", + visibility: [":__subpackages__"], + license_kinds: [ + "legacy_notice", + ], + license_text: [ + "src/external/IntelRDFPMathLib20U2/eula.txt", + ], +} + +license { + name: "external_realm_dep_mpark_variant_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-BSL-1.0", + ], + license_text: [ + "src/external/mpark/LICENSE.md", + ], +} + +license { + name: "external_realm_dep_nlohmann_json_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-MIT", + ], + license_text: [ + "src/external/json/LICENSE.MIT", + ], +} + +license { + name: "external_realm_dep_s2_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "src/external/s2/LICENSE-2.0.txt", + ], +} + +cc_object { + name: "IntelRDFPMathLib20U2", + srcs: [ + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_compare.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_mul.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_div.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_add.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_fma.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_string.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_2_str_tables.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid64_to_bid128.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_to_int64.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid128_quantize.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid_convert_data.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid_decimal_data.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid_decimal_globals.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid_from_int.c", + "src/external/IntelRDFPMathLib20U2/LIBRARY/src/bid_round.c", + ], +} + +cc_object { + name: "s2", + srcs: ["src/external/s2/**/*.cc"], + defaults: [ + "realm_consumer_defaults", + "realm_defaults", + ], + local_include_dirs: [ + "src", + "src/external", + "src/external/s2", + ], + cflags: [ + "-Wno-deprecated-declarations", + "-Wno-ignored-qualifiers", + "-Wno-macro-redefined", + "-Wno-missing-prototypes", + "-Wno-shorten-64-to-32", + "-Wno-undefined-var-template", + "-Wno-unknown-pragmas", + "-Wno-unused-const-variable", + "-Wno-unused-function", + "-Wno-unused-local-typedefs", + "-Wno-unused-parameter", + ], +} + +genrule { + name: "version_numbers.hpp", + srcs: [ + "dependencies.list", + "src/realm/version_numbers.hpp.in", + ], + out: ["realm/version_numbers.hpp"], + tool_files: ["tools/generate-version-numbers-for-soong.sh"], + cmd: "$(location) $(in) > $(out)", +} + +cc_defaults { + name: "realm_consumer_defaults", + cpp_std: "c++17", + rtti: true, + cppflags: [ + "-fexceptions", + ], + debug: { + cflags: ["-DREALM_DEBUG"], + }, + shared_libs: [ + "liblog", + "libandroid", + "libz", + "libcrypto", + "libssl", + ], +} + +cc_defaults { + name: "realm_defaults", + cflags: [ + "-fPIC", + "-DREALM_NO_CONFIG", + "-DREALM_HAVE_OPENSSL=1", + "-DREALM_ENABLE_ENCRYPTION=1", + "-DREALM_ENABLE_SYNC=1", + "-DREALM_ENABLE_GEOSPATIAL=1", + "-DREALM_HAVE_EPOLL=1", + "-Wno-non-virtual-dtor", + "-Wno-missing-field-initializers", + ], + lto: { + thin: true, + }, + include_build_directory: false, + generated_headers: ["version_numbers.hpp"], +} + +cc_library_static { + name: "realm", + defaults: [ + "realm_consumer_defaults", + "realm_defaults", + ], + cflags: [ + "-fvisibility=hidden", + ], + local_include_dirs: ["src/external"], + export_include_dirs: ["src"], + export_generated_headers: ["version_numbers.hpp"], + srcs: [ + ":IntelRDFPMathLib20U2", + ":s2", + "src/realm/**/*.cpp", + ], + exclude_srcs: [ + "src/realm/tools/**/*", + "src/realm/exec/**/*", + "src/realm/sync/tools/**/*", + "src/realm/object-store/c_api/**/*", + "src/realm/object-store/impl/apple/**/*", + "src/realm/object-store/impl/emscripten/**/*", + "src/realm/object-store/impl/generic/**/*", + "src/realm/object-store/impl/windows/**/*", + "src/realm/object-store/sync/impl/emscripten/**/*", + ], + export_shared_lib_headers: ["libcrypto"], +} diff --git a/src/external/json/LICENSE.MIT b/src/external/json/LICENSE.MIT new file mode 100644 index 00000000000..2fd5ad781bd --- /dev/null +++ b/src/external/json/LICENSE.MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-2022 Niels Lohmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/realm/sync/network/network_ssl.cpp b/src/realm/sync/network/network_ssl.cpp index 10c6a4096cc..933780bdc0b 100644 --- a/src/realm/sync/network/network_ssl.cpp +++ b/src/realm/sync/network/network_ssl.cpp @@ -371,7 +371,7 @@ void Context::ssl_use_verify_file(const std::string& path, std::error_code& ec) ec = std::error_code(); } -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) && !defined(OPENSSL_IS_BORINGSSL) class Stream::BioMethod { public: BIO_METHOD* bio_method; diff --git a/tools/generate-version-numbers-for-soong.sh b/tools/generate-version-numbers-for-soong.sh new file mode 100755 index 00000000000..2f24d3ff228 --- /dev/null +++ b/tools/generate-version-numbers-for-soong.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +source $1 + +version_and_extra=( ${VERSION//-/ } ) +version_only=${version_and_extra[0]} +extra=${version_and_extra[1]} + +semver=( ${version_only//./ } ) +major=${semver[0]} +minor=${semver[1]} +patch=${semver[2]} + +sed "s/@CONFIG_VERSION_MAJOR@/$major/g; s/@CONFIG_VERSION_MINOR@/$minor/g; s/@CONFIG_VERSION_PATCH@/$patch/g; s/@CONFIG_VERSION_TWEAK@/$extra/g; s/@CONFIG_VERSION@/$VERSION/g" $2 From df0b3851cabfeb1fe0f9c7da4bd89e2b1c8b8159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Wed, 20 Sep 2023 15:27:49 +0200 Subject: [PATCH 27/34] Logging mutations on tables (#6953) To avoid having the same operation logged twice, the logging in instruction_applier in removed. --- src/realm/cluster_tree.cpp | 4 +- src/realm/exec/realm_dump.c | 4 +- src/realm/group.cpp | 2 +- src/realm/mixed.cpp | 11 + src/realm/obj.cpp | 26 +- src/realm/obj.hpp | 3 +- src/realm/query.cpp | 11 +- src/realm/replication.cpp | 322 +++++++++++++++++- src/realm/replication.hpp | 102 +----- src/realm/sync/instruction_applier.cpp | 14 - src/realm/sync/instruction_applier.hpp | 33 +- src/realm/sync/instruction_replication.cpp | 6 +- src/realm/sync/instruction_replication.hpp | 2 +- src/realm/sync/noinst/client_history_impl.cpp | 2 +- .../sync/noinst/client_reset_recovery.cpp | 2 +- .../sync/noinst/server/server_history.cpp | 2 +- .../sync/tools/apply_to_state_command.cpp | 2 +- test/object-store/util/test_file.cpp | 1 + test/peer.hpp | 2 +- test/test_instruction_replication.cpp | 2 +- test/test_list.cpp | 8 +- test/test_stable_ids.cpp | 2 +- test/test_sync.cpp | 26 +- test/test_table.cpp | 43 +++ 24 files changed, 462 insertions(+), 170 deletions(-) diff --git a/src/realm/cluster_tree.cpp b/src/realm/cluster_tree.cpp index acada0071e0..23390a67ffe 100644 --- a/src/realm/cluster_tree.cpp +++ b/src/realm/cluster_tree.cpp @@ -987,8 +987,6 @@ size_t ClusterTree::get_ndx(ObjKey k) const noexcept void ClusterTree::erase(ObjKey k, CascadeState& state) { - m_owner->free_local_id_after_hash_collision(k); - m_owner->erase_from_search_indexes(k); if (!k.is_unresolved()) { if (auto table = get_owning_table()) { if (Replication* repl = table->get_repl()) { @@ -996,6 +994,8 @@ void ClusterTree::erase(ObjKey k, CascadeState& state) } } } + m_owner->free_local_id_after_hash_collision(k); + m_owner->erase_from_search_indexes(k); size_t root_size = m_root->erase(k, state); diff --git a/src/realm/exec/realm_dump.c b/src/realm/exec/realm_dump.c index ccdbdabe692..6bb6182014c 100644 --- a/src/realm/exec/realm_dump.c +++ b/src/realm/exec/realm_dump.c @@ -180,12 +180,12 @@ static int search_ref(FILE* fp, int64_t ref, int64_t target, size_t level, size_ char* buffer = malloc(byte_size * header.size); do_seek(fp, (size_t)(ref + 8), SEEK_SET); fread(buffer, byte_size * header.size, 1, fp); - for (size_t i = 0; i < header.size; i++) { + for (unsigned i = 0; i < header.size; i++) { stack[level] = i; int64_t subref = 1; switch (byte_size) { case 1: - subref = buffer[i]; + subref = ((int8_t*)buffer)[i]; break; case 2: subref = ((int16_t*)buffer)[i]; diff --git a/src/realm/group.cpp b/src/realm/group.cpp index 6531c6c8527..e46161048bf 100644 --- a/src/realm/group.cpp +++ b/src/realm/group.cpp @@ -852,7 +852,7 @@ void Group::remove_table(size_t table_ndx, TableKey key) size_t prior_num_tables = m_tables.size(); Replication* repl = *get_repl(); if (repl) - repl->erase_class(key, prior_num_tables); // Throws + repl->erase_class(key, table->get_name(), prior_num_tables); // Throws int64_t ref_64 = m_tables.get(table_ndx); REALM_ASSERT(!int_cast_has_overflow(ref_64)); diff --git a/src/realm/mixed.cpp b/src/realm/mixed.cpp index ce29fae0aa8..a1dccfb2332 100644 --- a/src/realm/mixed.cpp +++ b/src/realm/mixed.cpp @@ -822,6 +822,17 @@ std::ostream& operator<<(std::ostream& out, const Mixed& m) case type_Mixed: case type_LinkList: REALM_ASSERT(false); + default: + if (m.is_type(type_List)) { + out << "list"; + } + else if (m.is_type(type_Set)) { + out << "set"; + } + else if (m.is_type(type_Dictionary)) { + out << "dictionary"; + } + break; } } return out; diff --git a/src/realm/obj.cpp b/src/realm/obj.cpp index fb087809216..a430e798fc8 100644 --- a/src/realm/obj.cpp +++ b/src/realm/obj.cpp @@ -45,6 +45,8 @@ #include "realm/util/base64.hpp" #include "realm/util/overload.hpp" +#include + namespace realm { namespace { @@ -1035,6 +1037,27 @@ FullPath Obj::get_path() const return result; } +std::string Obj::get_id() const +{ + std::ostringstream ostr; + auto path = get_path(); + auto top_table = m_table->get_parent_group()->get_table(path.top_table); + ostr << top_table->get_class_name() << '['; + if (top_table->get_primary_key_column()) { + ostr << top_table->get_primary_key(path.top_objkey); + } + else { + ostr << path.top_objkey; + } + ostr << ']'; + if (!path.path_from_top.empty()) { + auto prop_name = top_table->get_column_name(path.path_from_top[0].get_col_key()); + path.path_from_top[0] = PathElement(prop_name); + ostr << path.path_from_top; + } + return ostr.str(); +} + Path Obj::get_short_path() const noexcept { return {}; @@ -1923,7 +1946,7 @@ Dictionary Obj::get_dictionary(ColKey col_key) const return Dictionary(Obj(*this), col_key); } -void Obj::set_collection(ColKey col_key, CollectionType type) +Obj& Obj::set_collection(ColKey col_key, CollectionType type) { REALM_ASSERT(col_key.get_type() == col_type_Mixed); update_if_needed(); @@ -1941,6 +1964,7 @@ void Obj::set_collection(ColKey col_key, CollectionType type) values.init_from_ref(ref); values.set_key(m_row_ndx, generate_key(0x10)); } + return *this; } DictionaryPtr Obj::get_dictionary_ptr(ColKey col_key) const diff --git a/src/realm/obj.hpp b/src/realm/obj.hpp index 3f6926de540..4db877aac95 100644 --- a/src/realm/obj.hpp +++ b/src/realm/obj.hpp @@ -74,6 +74,7 @@ class Obj : public CollectionParent { // If you need to obtain additional information for each object in the path, // you should use get_fat_path() or traverse_path() instead (see below). FullPath get_path() const final; + std::string get_id() const; Path get_short_path() const noexcept final; StablePath get_stable_path() const noexcept final; void add_index(Path& path, const Index& ndx) const final; @@ -308,7 +309,7 @@ class Obj : public CollectionParent { Dictionary get_dictionary(ColKey col_key) const; Dictionary get_dictionary(StringData col_name) const; - void set_collection(ColKey col_key, CollectionType type); + Obj& set_collection(ColKey col_key, CollectionType type); DictionaryPtr get_dictionary_ptr(ColKey col_key) const; DictionaryPtr get_dictionary_ptr(const Path& path) const; diff --git a/src/realm/query.cpp b/src/realm/query.cpp index e4b8208b54b..08c0e873997 100644 --- a/src/realm/query.cpp +++ b/src/realm/query.cpp @@ -1737,6 +1737,9 @@ std::string Query::validate() const std::string Query::get_description(util::serializer::SerialisationState& state) const { + if (m_view) { + throw SerializationError("Serialization of a query constrained by a view is not currently supported"); + } std::string description; if (auto root = root_node()) { description = root->describe_expression(state); @@ -1765,9 +1768,6 @@ util::bind_ptr Query::get_ordering() std::string Query::get_description() const { - if (m_view) { - throw SerializationError("Serialization of a query constrained by a view is not currently supported"); - } util::serializer::SerialisationState state(m_table->get_parent_group()); return get_description(state); } @@ -1778,7 +1778,10 @@ std::string Query::get_description_safe() const noexcept util::serializer::SerialisationState state(m_table->get_parent_group()); return get_description(state); } - catch (...) { + catch (const Exception& e) { + if (auto logger = m_table->get_logger()) { + logger->log(util::Logger::Level::warn, "Query::get_description() failed: '%1'", e.what()); + } } return "Unknown Query"; } diff --git a/src/realm/replication.cpp b/src/realm/replication.cpp index 934945a7efc..a05bcfff142 100644 --- a/src/realm/replication.cpp +++ b/src/realm/replication.cpp @@ -17,8 +17,10 @@ **************************************************************************/ #include +#include #include +#include #include using namespace realm; @@ -62,36 +64,319 @@ Replication::version_type Replication::prepare_commit(version_type orig_version) return new_version; } -void Replication::add_class(TableKey table_key, StringData, Table::Type) +void Replication::add_class(TableKey table_key, StringData name, Table::Type type) { + if (auto logger = get_logger()) { + if (type == Table::Type::Embedded) { + logger->log(util::Logger::Level::debug, "Add %1 class '%2'", type, name); + } + else { + logger->log(util::Logger::Level::debug, "Add class '%1'", name); + } + } unselect_all(); m_encoder.insert_group_level_table(table_key); // Throws } -void Replication::add_class_with_primary_key(TableKey tk, StringData, DataType, StringData, bool, +void Replication::add_class_with_primary_key(TableKey tk, StringData name, DataType pk_type, StringData pk_name, bool, Table::Type table_type) { + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::debug, "Add %1 class '%2' with primary key property '%3' of %4", table_type, + Group::table_name_to_class_name(name), pk_name, pk_type); + } REALM_ASSERT(table_type != Table::Type::Embedded); unselect_all(); m_encoder.insert_group_level_table(tk); // Throws } +void Replication::erase_class(TableKey tk, StringData table_name, size_t) +{ + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::debug, "Remove class '%1'", Group::table_name_to_class_name(table_name)); + } + unselect_all(); + m_encoder.erase_class(tk); // Throws +} + + +void Replication::insert_column(const Table* t, ColKey col_key, DataType type, StringData col_name, + Table* target_table) +{ + if (auto logger = get_logger()) { + const char* collection_type = ""; + if (col_key.is_collection()) { + if (col_key.is_list()) { + collection_type = "list "; + } + else if (col_key.is_dictionary()) { + collection_type = "dictionary "; + } + else { + collection_type = "set "; + } + } + if (target_table) { + logger->log(util::Logger::Level::debug, "On class '%1': Add property '%2' %3linking '%4'", + t->get_class_name(), col_name, collection_type, target_table->get_class_name()); + } + else { + logger->log(util::Logger::Level::debug, "On class '%1': Add property '%2' %3of %4", t->get_class_name(), + col_name, collection_type, type); + } + } + select_table(t); // Throws + m_encoder.insert_column(col_key); // Throws +} + +void Replication::erase_column(const Table* t, ColKey col_key) +{ + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::debug, "On class '%1': Remove property '%2'", t->get_class_name(), + t->get_column_name(col_key)); + } + select_table(t); // Throws + m_encoder.erase_column(col_key); // Throws +} + void Replication::create_object(const Table* t, GlobalKey id) { + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::debug, "Create object '%1'", t->get_class_name()); + } select_table(t); // Throws m_encoder.create_object(id.get_local_key(0)); // Throws } -void Replication::create_object_with_primary_key(const Table* t, ObjKey key, Mixed) +void Replication::create_object_with_primary_key(const Table* t, ObjKey key, Mixed pk) { + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::debug, "Create object '%1' with primary key %2", t->get_class_name(), pk); + } select_table(t); // Throws m_encoder.create_object(key); // Throws } +void Replication::remove_object(const Table* t, ObjKey key) +{ + if (auto logger = get_logger()) { + if (t->is_embedded()) { + logger->log(util::Logger::Level::debug, "Remove embedded object '%1'", t->get_class_name()); + } + else if (t->get_primary_key_column()) { + logger->log(util::Logger::Level::debug, "Remove object '%1' with primary key %2", t->get_class_name(), + t->get_primary_key(key)); + } + else { + logger->log(util::Logger::Level::debug, "Remove object '%1'[%2]", t->get_class_name(), key); + } + } + select_table(t); // Throws + m_encoder.remove_object(key); // Throws +} + +inline void Replication::select_obj(ObjKey key) +{ + if (key == m_selected_obj) { + return; + } + if (auto logger = get_logger()) { + if (logger->would_log(util::Logger::Level::debug)) { + auto class_name = m_selected_table->get_class_name(); + if (m_selected_table->get_primary_key_column()) { + auto pk = m_selected_table->get_primary_key(key); + logger->log(util::Logger::Level::debug, "Mutating object '%1' with primary key %2", class_name, pk); + } + else if (m_selected_table->is_embedded()) { + auto obj = m_selected_table->get_object(key); + logger->log(util::Logger::Level::debug, "Mutating object '%1' with path '%2'", class_name, + obj.get_id()); + } + else { + logger->log(util::Logger::Level::debug, "Mutating anonymous object '%1'[%2]", class_name, key); + } + } + } + m_selected_obj = key; +} + +void Replication::do_set(const Table* t, ColKey col_key, ObjKey key, _impl::Instruction variant) +{ + if (variant != _impl::Instruction::instr_SetDefault) { + select_table(t); // Throws + select_obj(key); + m_encoder.modify_object(col_key, key); // Throws + } +} + +void Replication::set(const Table* t, ColKey col_key, ObjKey key, Mixed value, _impl::Instruction variant) +{ + do_set(t, col_key, key, variant); // Throws + if (auto logger = get_logger()) { + if (logger->would_log(util::Logger::Level::trace)) { + if (col_key.get_type() == col_type_Link && value.is_type(type_Link)) { + auto target_table = t->get_opposite_table(col_key); + if (target_table->is_embedded()) { + logger->log(util::Logger::Level::trace, " Creating embedded object '%1' in '%2'", + target_table->get_class_name(), t->get_column_name(col_key)); + } + else if (target_table->get_primary_key_column()) { + auto link = value.get(); + auto pk = target_table->get_primary_key(link); + logger->log(util::Logger::Level::trace, " Linking object '%1' with primary key %2 from '%3'", + target_table->get_class_name(), pk, t->get_column_name(col_key)); + } + else { + logger->log(util::Logger::Level::trace, " Linking object '%1'[%2] from '%3'", + target_table->get_class_name(), key, t->get_column_name(col_key)); + } + } + else { + logger->log(util::Logger::Level::trace, " Set '%1' to %2", t->get_column_name(col_key), value); + } + } + } +} + +void Replication::nullify_link(const Table* t, ColKey col_key, ObjKey key) +{ + select_table(t); // Throws + select_obj(key); + m_encoder.modify_object(col_key, key); // Throws + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::trace, " Nullify '%1'", t->get_column_name(col_key)); + } +} + +void Replication::add_int(const Table* t, ColKey col_key, ObjKey key, int_fast64_t value) +{ + do_set(t, col_key, key); // Throws + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::trace, " Adding %1 to '%2'", value, t->get_column_name(col_key)); + } +} + + +Path Replication::get_prop_name(Path&& path) const +{ + Path ret(std::move(path)); + auto col_key = ret[0].get_col_key(); + auto prop_name = m_selected_table->get_column_name(col_key); + ret[0] = PathElement(prop_name); + return ret; +} + +void Replication::log_collection_operation(const char* operation, const CollectionBase& collection, Mixed value, + Mixed index) const +{ + auto logger = get_logger(); + auto path = collection.get_short_path(); + auto col_key = path[0].get_col_key(); + auto prop_name = m_selected_table->get_column_name(col_key); + path[0] = PathElement(prop_name); + std::string position; + if (!index.is_null()) { + position = util::format(" at position %1", index); + } + if (Table::is_link_type(col_key.get_type()) && value.is_type(type_Link)) { + auto target_table = m_selected_table->get_opposite_table(col_key); + if (target_table->is_embedded()) { + logger->log(util::Logger::Level::trace, " %1 embedded object '%2' in %3%4 ", operation, + target_table->get_class_name(), path, position); + } + else if (target_table->get_primary_key_column()) { + auto link = value.get(); + auto pk = target_table->get_primary_key(link); + logger->log(util::Logger::Level::trace, " %1 object '%2' with primary key %3 in %4%5", operation, + target_table->get_class_name(), pk, path, position); + } + else { + auto link = value.get(); + logger->log(util::Logger::Level::trace, " %1 object '%2'[%3] in %4%5", operation, + target_table->get_class_name(), link, path, position); + } + } + else { + logger->log(util::Logger::Level::trace, " %1 %2 in %3%4", operation, value, path, position); + } +} +void Replication::list_insert(const CollectionBase& list, size_t list_ndx, Mixed value, size_t) +{ + select_collection(list); // Throws + m_encoder.collection_insert(list.translate_index(list_ndx)); // Throws + if (auto logger = get_logger()) { + if (logger->would_log(util::Logger::Level::trace)) { + log_collection_operation("Insert", list, value, int64_t(list_ndx)); + } + } +} + +void Replication::list_set(const CollectionBase& list, size_t list_ndx, Mixed value) +{ + select_collection(list); // Throws + m_encoder.collection_set(list.translate_index(list_ndx)); // Throws + if (auto logger = get_logger()) { + if (logger->would_log(util::Logger::Level::trace)) { + log_collection_operation("Set", list, value, int64_t(list_ndx)); + } + } +} + +void Replication::list_erase(const CollectionBase& list, size_t link_ndx) +{ + select_collection(list); // Throws + m_encoder.collection_erase(list.translate_index(link_ndx)); // Throws + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::trace, " Erase '%1' at position %2", get_prop_name(list.get_short_path()), + link_ndx); + } +} + +void Replication::list_move(const CollectionBase& list, size_t from_link_ndx, size_t to_link_ndx) +{ + select_collection(list); // Throws + m_encoder.collection_move(list.translate_index(from_link_ndx), list.translate_index(to_link_ndx)); // Throws + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::trace, " Move %1 to %2 in '%3'", from_link_ndx, to_link_ndx, + get_prop_name(list.get_short_path())); + } +} + +void Replication::set_insert(const CollectionBase& set, size_t set_ndx, Mixed value) +{ + select_collection(set); // Throws + m_encoder.collection_insert(set_ndx); // Throws + if (auto logger = get_logger()) { + if (logger->would_log(util::Logger::Level::trace)) { + log_collection_operation("Insert", set, value, Mixed()); + } + } +} + +void Replication::set_erase(const CollectionBase& set, size_t set_ndx, Mixed value) +{ + select_collection(set); // Throws + m_encoder.collection_erase(set_ndx); // Throws + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::trace, " Erase %1 from '%2'", value, get_prop_name(set.get_short_path())); + } +} + +void Replication::set_clear(const CollectionBase& set) +{ + select_collection(set); // Throws + m_encoder.collection_clear(set.size()); // Throws + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::trace, " Clear '%1'", get_prop_name(set.get_short_path())); + } +} + void Replication::do_select_table(const Table* table) { m_encoder.select_table(table->get_key()); // Throws m_selected_table = table; + m_selected_list = CollectionId(); + m_selected_obj = ObjKey(); } void Replication::do_select_collection(const CollectionBase& list) @@ -101,6 +386,8 @@ void Replication::do_select_collection(const CollectionBase& list) ObjKey key = list.get_owner_key(); auto path = list.get_stable_path(); + select_obj(key); + m_encoder.select_collection(col_key, key, path); // Throws m_selected_list = CollectionId(list.get_table()->get_key(), key, std::move(path)); } @@ -109,34 +396,57 @@ void Replication::list_clear(const CollectionBase& list) { select_collection(list); // Throws m_encoder.collection_clear(list.size()); // Throws + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::trace, " Clear '%1'", get_prop_name(list.get_short_path())); + } } void Replication::link_list_nullify(const Lst& list, size_t link_ndx) { select_collection(list); m_encoder.collection_erase(link_ndx); + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::trace, " Nullify '%1' position %2", + m_selected_table->get_column_name(list.get_col_key()), link_ndx); + } } -void Replication::dictionary_insert(const CollectionBase& dict, size_t ndx, Mixed, Mixed) +void Replication::dictionary_insert(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value) { select_collection(dict); m_encoder.collection_insert(ndx); + if (auto logger = get_logger()) { + if (logger->would_log(util::Logger::Level::trace)) { + log_collection_operation("Insert", dict, value, key); + } + } } -void Replication::dictionary_set(const CollectionBase& dict, size_t ndx, Mixed, Mixed) +void Replication::dictionary_set(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value) { select_collection(dict); m_encoder.collection_set(ndx); + if (auto logger = get_logger()) { + if (logger->would_log(util::Logger::Level::trace)) { + log_collection_operation("Set", dict, value, key); + } + } } -void Replication::dictionary_erase(const CollectionBase& dict, size_t ndx, Mixed) +void Replication::dictionary_erase(const CollectionBase& dict, size_t ndx, Mixed key) { select_collection(dict); m_encoder.collection_erase(ndx); + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::trace, " Erase %1 from '%2'", key, get_prop_name(dict.get_short_path())); + } } void Replication::dictionary_clear(const CollectionBase& dict) { select_collection(dict); m_encoder.collection_clear(dict.size()); + if (auto logger = get_logger()) { + logger->log(util::Logger::Level::trace, " Clear '%1'", get_prop_name(dict.get_short_path())); + } } diff --git a/src/realm/replication.hpp b/src/realm/replication.hpp index 6a721bad960..437d6654901 100644 --- a/src/realm/replication.hpp +++ b/src/realm/replication.hpp @@ -49,7 +49,7 @@ class Replication { virtual void add_class(TableKey table_key, StringData table_name, Table::Type table_type); virtual void add_class_with_primary_key(TableKey, StringData table_name, DataType pk_type, StringData pk_field, bool nullable, Table::Type table_type); - virtual void erase_class(TableKey table_key, size_t num_tables); + virtual void erase_class(TableKey, StringData table_name, size_t num_tables); virtual void rename_class(TableKey table_key, StringData new_name); virtual void insert_column(const Table*, ColKey col_key, DataType type, StringData name, Table* target_table); virtual void erase_column(const Table*, ColKey col_key); @@ -422,17 +422,21 @@ class Replication { _impl::TransactLogEncoder m_encoder{m_stream}; util::Logger* m_logger = nullptr; mutable const Table* m_selected_table = nullptr; + mutable ObjKey m_selected_obj; mutable CollectionId m_selected_list; void unselect_all() noexcept; - void select_table(const Table*); // unselects link list + void select_table(const Table*); // unselects link list and obj + void select_obj(ObjKey key); void select_collection(const CollectionBase&); void do_select_table(const Table*); void do_select_collection(const CollectionBase&); void do_set(const Table*, ColKey col_key, ObjKey key, _impl::Instruction variant = _impl::instr_Set); - + void log_collection_operation(const char* operation, const CollectionBase& collection, Mixed value, + Mixed index) const; + Path get_prop_name(Path&&) const; size_t transact_log_size(); }; @@ -484,7 +488,6 @@ inline void Replication::select_table(const Table* table) { if (table != m_selected_table) do_select_table(table); // Throws - m_selected_list = CollectionId(); } inline void Replication::select_collection(const CollectionBase& list) @@ -494,109 +497,18 @@ inline void Replication::select_collection(const CollectionBase& list) } } -inline void Replication::erase_class(TableKey table_key, size_t) -{ - unselect_all(); - m_encoder.erase_class(table_key); // Throws -} - inline void Replication::rename_class(TableKey table_key, StringData) { unselect_all(); m_encoder.rename_class(table_key); // Throws } -inline void Replication::insert_column(const Table* t, ColKey col_key, DataType, StringData, Table*) -{ - select_table(t); // Throws - m_encoder.insert_column(col_key); // Throws -} - -inline void Replication::erase_column(const Table* t, ColKey col_key) -{ - select_table(t); // Throws - m_encoder.erase_column(col_key); // Throws -} - - inline void Replication::rename_column(const Table* t, ColKey col_key, StringData) { select_table(t); // Throws m_encoder.rename_column(col_key); // Throws } -inline void Replication::do_set(const Table* t, ColKey col_key, ObjKey key, _impl::Instruction variant) -{ - if (variant != _impl::Instruction::instr_SetDefault) { - select_table(t); // Throws - m_encoder.modify_object(col_key, key); // Throws - } -} - -inline void Replication::set(const Table* t, ColKey col_key, ObjKey key, Mixed, _impl::Instruction variant) -{ - do_set(t, col_key, key, variant); // Throws -} - -inline void Replication::add_int(const Table* t, ColKey col_key, ObjKey key, int_fast64_t) -{ - do_set(t, col_key, key); // Throws -} - -inline void Replication::nullify_link(const Table* t, ColKey col_key, ObjKey key) -{ - select_table(t); // Throws - m_encoder.modify_object(col_key, key); // Throws -} - -inline void Replication::list_set(const CollectionBase& list, size_t list_ndx, Mixed) -{ - select_collection(list); // Throws - m_encoder.collection_set(list.translate_index(list_ndx)); // Throws -} - -inline void Replication::list_insert(const CollectionBase& list, size_t list_ndx, Mixed, size_t) -{ - select_collection(list); // Throws - m_encoder.collection_insert(list.translate_index(list_ndx)); // Throws -} - -inline void Replication::set_insert(const CollectionBase& set, size_t set_ndx, Mixed) -{ - select_collection(set); // Throws - m_encoder.collection_insert(set_ndx); // Throws -} - -inline void Replication::set_erase(const CollectionBase& set, size_t set_ndx, Mixed) -{ - select_collection(set); // Throws - m_encoder.collection_erase(set_ndx); // Throws -} - -inline void Replication::set_clear(const CollectionBase& set) -{ - select_collection(set); // Throws - m_encoder.collection_clear(set.size()); // Throws -} - -inline void Replication::remove_object(const Table* t, ObjKey key) -{ - select_table(t); // Throws - m_encoder.remove_object(key); // Throws -} - -inline void Replication::list_move(const CollectionBase& list, size_t from_link_ndx, size_t to_link_ndx) -{ - select_collection(list); // Throws - m_encoder.collection_move(list.translate_index(from_link_ndx), list.translate_index(to_link_ndx)); // Throws -} - -inline void Replication::list_erase(const CollectionBase& list, size_t link_ndx) -{ - select_collection(list); // Throws - m_encoder.collection_erase(list.translate_index(link_ndx)); // Throws -} - inline void Replication::typed_link_change(const Table* source_table, ColKey col, TableKey dest_table) { select_table(source_table); diff --git a/src/realm/sync/instruction_applier.cpp b/src/realm/sync/instruction_applier.cpp index 1ce3642f11b..98f29bcdc28 100644 --- a/src/realm/sync/instruction_applier.cpp +++ b/src/realm/sync/instruction_applier.cpp @@ -122,7 +122,6 @@ void InstructionApplier::operator()(const Instruction::AddTable& instr) [&](const Instruction::AddTable::TopLevelTable& spec) { auto table_type = (spec.is_asymmetric ? Table::Type::TopLevelAsymmetric : Table::Type::TopLevel); if (spec.pk_type == Instruction::Payload::Type::GlobalKey) { - log("sync::create_table(group, \"%1\", %2);", table_name, table_type); m_transaction.get_or_add_table(table_name, table_type); } else { @@ -134,8 +133,6 @@ void InstructionApplier::operator()(const Instruction::AddTable& instr) StringData pk_field = get_string(spec.pk_field); bool nullable = spec.pk_nullable; - log("group.get_or_add_table_with_primary_key(group, \"%1\", %2, \"%3\", %4, %5);", table_name, - pk_type, pk_field, nullable, table_type); if (!m_transaction.get_or_add_table_with_primary_key(table_name, pk_type, pk_field, nullable, table_type)) { bad_transaction_log("AddTable: The existing table '%1' has different properties", table_name); @@ -149,7 +146,6 @@ void InstructionApplier::operator()(const Instruction::AddTable& instr) } } else { - log("group.add_embedded_table(\"%1\");", table_name); m_transaction.add_table(table_name, Table::Type::Embedded); } }, @@ -169,7 +165,6 @@ void InstructionApplier::operator()(const Instruction::EraseTable& instr) bad_transaction_log("table does not exist"); } - log("sync::erase_table(m_group, \"%1\")", table_name); m_transaction.remove_table(table_name); } @@ -188,8 +183,6 @@ void InstructionApplier::operator()(const Instruction::CreateObject& instr) if (!table->is_nullable(pk_col)) { bad_transaction_log("CreateObject(NULL) on a table with a non-nullable primary key"); } - log("sync::create_object_with_primary_key(group, get_table(\"%1\"), realm::util::none);", - table->get_name()); m_last_object = table->create_object_with_primary_key(util::none); }, [&](int64_t pk) { @@ -200,7 +193,6 @@ void InstructionApplier::operator()(const Instruction::CreateObject& instr) bad_transaction_log("CreateObject(Int) on a table with primary key type %1", table->get_column_type(pk_col)); } - log("sync::create_object_with_primary_key(group, get_table(\"%1\"), %2);", table->get_name(), pk); m_last_object = table->create_object_with_primary_key(pk); }, [&](InternString pk) { @@ -212,8 +204,6 @@ void InstructionApplier::operator()(const Instruction::CreateObject& instr) table->get_column_type(pk_col)); } StringData str = get_string(pk); - log("sync::create_object_with_primary_key(group, get_table(\"%1\"), \"%2\");", table->get_name(), - str); m_last_object = table->create_object_with_primary_key(str); }, [&](const ObjectId& id) { @@ -224,7 +214,6 @@ void InstructionApplier::operator()(const Instruction::CreateObject& instr) bad_transaction_log("CreateObject(ObjectId) on a table with primary key type %1", table->get_column_type(pk_col)); } - log("sync::create_object_with_primary_key(group, get_table(\"%1\"), %2);", table->get_name(), id); m_last_object = table->create_object_with_primary_key(id); }, [&](const UUID& id) { @@ -235,15 +224,12 @@ void InstructionApplier::operator()(const Instruction::CreateObject& instr) bad_transaction_log("CreateObject(UUID) on a table with primary key type %1", table->get_column_type(pk_col)); } - log("sync::create_object_with_primary_key(group, get_table(\"%1\"), %2);", table->get_name(), id); m_last_object = table->create_object_with_primary_key(id); }, [&](GlobalKey key) { if (pk_col) { bad_transaction_log("CreateObject(GlobalKey) on table with a primary key"); } - log("sync::create_object_with_primary_key(group, get_table(\"%1\"), GlobalKey{%2, %3});", - table->get_name(), key.hi(), key.lo()); m_last_object = table->create_object(key); }, }, diff --git a/src/realm/sync/instruction_applier.hpp b/src/realm/sync/instruction_applier.hpp index e41b6119487..6861f4d9ea8 100644 --- a/src/realm/sync/instruction_applier.hpp +++ b/src/realm/sync/instruction_applier.hpp @@ -40,9 +40,9 @@ struct InstructionApplier { /// /// FIXME: Consider using std::error_code instead of throwing /// BadChangesetError. - void apply(const Changeset&, util::Logger*); + void apply(const Changeset&); - void begin_apply(const Changeset&, util::Logger*) noexcept; + void begin_apply(const Changeset&) noexcept; void end_apply() noexcept; protected: @@ -59,24 +59,16 @@ struct InstructionApplier { friend struct Instruction; // to allow visitor template - static void apply(A& applier, const Changeset&, util::Logger*); + static void apply(A& applier, const Changeset&); // Allows for in-place modification of changeset while applying it template - static void apply(A& applier, Changeset&, util::Logger*); + static void apply(A& applier, Changeset&); TableRef table_for_class_name(StringData) const; // Throws Transaction& m_transaction; - template - void log(const char* fmt, Args&&... args) - { - if (m_logger) { - m_logger->trace(fmt, std::forward(args)...); // Throws - } - } - bool check_links_exist(const Instruction::Payload& payload); bool allows_null_links(const Instruction::PathInstruction& instr, const std::string_view& instr_name); std::string to_string(const Instruction::PathInstruction& instr) const; @@ -122,7 +114,6 @@ struct InstructionApplier { private: const Changeset* m_log = nullptr; - util::Logger* m_logger = nullptr; Group::TableNameBuffer m_table_name_buffer; InternString m_last_table_name; @@ -156,16 +147,14 @@ inline InstructionApplier::InstructionApplier(Transaction& group) noexcept { } -inline void InstructionApplier::begin_apply(const Changeset& log, util::Logger* logger) noexcept +inline void InstructionApplier::begin_apply(const Changeset& log) noexcept { m_log = &log; - m_logger = logger; } inline void InstructionApplier::end_apply() noexcept { m_log = nullptr; - m_logger = nullptr; m_last_table_name = InternString{}; m_last_field_name = InternString{}; m_last_table = TableRef{}; @@ -176,9 +165,9 @@ inline void InstructionApplier::end_apply() noexcept } template -inline void InstructionApplier::apply(A& applier, const Changeset& changeset, util::Logger* logger) +inline void InstructionApplier::apply(A& applier, const Changeset& changeset) { - applier.begin_apply(changeset, logger); + applier.begin_apply(changeset); for (auto instr : changeset) { if (!instr) continue; @@ -188,9 +177,9 @@ inline void InstructionApplier::apply(A& applier, const Changeset& changeset, ut } template -inline void InstructionApplier::apply(A& applier, Changeset& changeset, util::Logger* logger) +inline void InstructionApplier::apply(A& applier, Changeset& changeset) { - applier.begin_apply(changeset, logger); + applier.begin_apply(changeset); for (auto instr : changeset) { if (!instr) continue; @@ -202,9 +191,9 @@ inline void InstructionApplier::apply(A& applier, Changeset& changeset, util::Lo applier.end_apply(); } -inline void InstructionApplier::apply(const Changeset& log, util::Logger* logger) +inline void InstructionApplier::apply(const Changeset& log) { - apply(*this, log, logger); // Throws + apply(*this, log); // Throws } } // namespace sync diff --git a/src/realm/sync/instruction_replication.cpp b/src/realm/sync/instruction_replication.cpp index 67cdfd55c2a..ef3c17c1701 100644 --- a/src/realm/sync/instruction_replication.cpp +++ b/src/realm/sync/instruction_replication.cpp @@ -314,11 +314,9 @@ void SyncReplication::create_object_with_primary_key(const Table* table, ObjKey } -void SyncReplication::erase_class(TableKey table_key, size_t num_tables) +void SyncReplication::erase_class(TableKey table_key, StringData table_name, size_t num_tables) { - Replication::erase_class(table_key, num_tables); - - StringData table_name = m_transaction->get_table_name(table_key); + Replication::erase_class(table_key, table_name, num_tables); bool is_class = m_transaction->table_is_public(table_key); diff --git a/src/realm/sync/instruction_replication.hpp b/src/realm/sync/instruction_replication.hpp index aaad77a8250..b9008b9609b 100644 --- a/src/realm/sync/instruction_replication.hpp +++ b/src/realm/sync/instruction_replication.hpp @@ -51,7 +51,7 @@ class SyncReplication : public Replication { void create_object(const Table*, GlobalKey) final; void create_object_with_primary_key(const Table*, ObjKey, Mixed) final; - void erase_class(TableKey table_key, size_t num_tables) final; + void erase_class(TableKey table_key, StringData table_name, size_t num_tables) final; void rename_class(TableKey table_key, StringData new_name) final; void insert_column(const Table*, ColKey col_key, DataType type, StringData name, Table* target_table) final; void erase_column(const Table*, ColKey col_key) final; diff --git a/src/realm/sync/noinst/client_history_impl.cpp b/src/realm/sync/noinst/client_history_impl.cpp index b227a367b06..527f3f3bf2b 100644 --- a/src/realm/sync/noinst/client_history_impl.cpp +++ b/src/realm/sync/noinst/client_history_impl.cpp @@ -552,7 +552,7 @@ size_t ClientHistory::transform_and_apply_server_changesets(util::Spanoriginal_changeset_size; diff --git a/src/realm/sync/noinst/client_reset_recovery.cpp b/src/realm/sync/noinst/client_reset_recovery.cpp index 7896b88466c..4d90783da61 100644 --- a/src/realm/sync/noinst/client_reset_recovery.cpp +++ b/src/realm/sync/noinst/client_reset_recovery.cpp @@ -434,7 +434,7 @@ void RecoverLocalChangesetsHandler::process_changesets(const std::vector bool { TempShortCircuitReplication tdr{*this}; // Short-circuit while integrating changes InstructionApplier applier{transaction}; - applier.apply(*c, &logger); + applier.apply(*c); reset(); // Reset the instruction encoder return true; }; diff --git a/src/realm/sync/tools/apply_to_state_command.cpp b/src/realm/sync/tools/apply_to_state_command.cpp index 0ed12e17849..3a6aa01498d 100644 --- a/src/realm/sync/tools/apply_to_state_command.cpp +++ b/src/realm/sync/tools/apply_to_state_command.cpp @@ -307,7 +307,7 @@ int main(int argc, const char** argv) }); auto transaction = local_db->start_write(); realm::sync::InstructionApplier applier(*transaction); - applier.apply(changeset, logger.get()); + applier.apply(changeset); auto generated_version = transaction->commit(); logger->debug("integrated local changesets as version %1", generated_version); history.set_local_origin_timestamp_source(realm::sync::generate_changeset_timestamp); diff --git a/test/object-store/util/test_file.cpp b/test/object-store/util/test_file.cpp index 2e518b8b9d3..f0f560a8864 100644 --- a/test/object-store/util/test_file.cpp +++ b/test/object-store/util/test_file.cpp @@ -114,6 +114,7 @@ InMemoryTestFile::InMemoryTestFile() in_memory = true; schema_version = 0; encryption_key = std::vector(); + util::Logger::set_default_level_threshold(realm::util::Logger::Level::TEST_LOGGING_LEVEL); } DBOptions InMemoryTestFile::options() const diff --git a/test/peer.hpp b/test/peer.hpp index 84a86a9e8e4..2ff1334866b 100644 --- a/test/peer.hpp +++ b/test/peer.hpp @@ -488,7 +488,7 @@ inline auto ShortCircuitHistory::integrate_remote_changesets(file_ident_type rem TransformHistoryImpl transform_hist{*this, remote_file_ident}; auto apply = [&](const Changeset* c) -> bool { sync::InstructionApplier applier{*transact}; - applier.apply(*c, &logger); + applier.apply(*c); return true; }; diff --git a/test/test_instruction_replication.cpp b/test/test_instruction_replication.cpp index aeb8e4ae6a1..3c1c7239916 100644 --- a/test/test_instruction_replication.cpp +++ b/test/test_instruction_replication.cpp @@ -45,7 +45,7 @@ struct Fixture { WriteTransaction wt{sg_2}; InstructionApplier applier{wt}; - applier.apply(result, test_context.logger.get()); + applier.apply(result); wt.commit(); } diff --git a/test/test_list.cpp b/test/test_list.cpp index 169031f29d1..e540eb7450a 100644 --- a/test/test_list.cpp +++ b/test/test_list.cpp @@ -637,12 +637,14 @@ TEST(List_Nested_InMixed) { SHARED_GROUP_TEST_PATH(path); std::string message; - DBRef db = DB::create(make_in_realm_history(), path); + DBOptions options; + options.logger = test_context.logger; + DBRef db = DB::create(make_in_realm_history(), path, options); auto tr = db->start_write(); - auto table = tr->add_table("table"); + auto table = tr->add_table_with_primary_key("table", type_Int, "id"); auto col_any = table->add_column(type_Mixed, "something"); - Obj obj = table->create_object(); + Obj obj = table->create_object_with_primary_key(1); obj.set_collection(col_any, CollectionType::Dictionary); auto set = obj.get_set_ptr(col_any); diff --git a/test/test_stable_ids.cpp b/test/test_stable_ids.cpp index 6ef8a92256a..e28a6cf7896 100644 --- a/test/test_stable_ids.cpp +++ b/test/test_stable_ids.cpp @@ -213,7 +213,7 @@ TEST(StableIDs_ChangesGlobalObjectIdWhenPeerIdReceived) WriteTransaction wt{sg_2}; InstructionApplier applier{wt}; - applier.apply(result, test_context.logger.get()); + applier.apply(result); wt.commit(); // Check same invariants as above. diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 1be1dc88063..b541c3ca2b9 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -5861,9 +5861,12 @@ NONCONCURRENT_TEST_TYPES(Sync_PrimaryKeyTypes, Int, String, ObjectId, UUID, util TEST(Sync_Mixed) { // Test replication and synchronization of Mixed values and lists. - - TEST_CLIENT_DB(db_1); - TEST_CLIENT_DB(db_2); + DBOptions options; + options.logger = test_context.logger; + SHARED_GROUP_TEST_PATH(db_1_path); + SHARED_GROUP_TEST_PATH(db_2_path); + auto db_1 = DB::create(make_client_replication(), db_1_path, options); + auto db_2 = DB::create(make_client_replication(), db_2_path, options); TEST_DIR(dir); fixtures::ClientServerFixture fixture{dir, test_context}; @@ -6723,10 +6726,12 @@ TEST(Sync_BundledRealmFile) TEST(Sync_UpgradeToClientHistory) { - SHARED_GROUP_TEST_PATH(db1_path); - SHARED_GROUP_TEST_PATH(db2_path); - auto db_1 = DB::create(make_in_realm_history(), db1_path); - auto db_2 = DB::create(make_in_realm_history(), db2_path); + DBOptions options; + options.logger = test_context.logger; + SHARED_GROUP_TEST_PATH(db_1_path); + SHARED_GROUP_TEST_PATH(db_2_path); + auto db_1 = DB::create(make_in_realm_history(), db_1_path, options); + auto db_2 = DB::create(make_in_realm_history(), db_2_path, options); { auto tr = db_1->start_write(); @@ -6762,16 +6767,23 @@ TEST(Sync_UpgradeToClientHistory) auto list = baa.get_list(col_list); list.add(1); + list.add(0); list.add(2); list.add(3); + list.set(1, 5); + list.remove(1); auto set = baa.get_set(col_set); set.insert(4); + set.insert(2); set.insert(5); set.insert(6); + set.erase(2); auto dict = baa.get_dictionary(col_dict); + dict.insert("key6", 6); dict.insert("key7", 7); dict.insert("key8", 8); dict.insert("key9", 9); + dict.erase("key6"); for (int i = 0; i < 100; i++) { foobaas->create_object_with_primary_key(ObjectId::gen()).set(col_time, Timestamp(::time(nullptr), i)); diff --git a/test/test_table.cpp b/test/test_table.cpp index db5206d05d5..91377bad89a 100644 --- a/test/test_table.cpp +++ b/test/test_table.cpp @@ -5360,4 +5360,47 @@ TEST(Table_FullTextIndex) CHECK_EQUAL(2, res.size()); } +TEST(Table_LoggingMutations) +{ + std::stringstream buffer; + SHARED_GROUP_TEST_PATH(path); + DBOptions options; + options.logger = std::make_shared(buffer); + options.logger->set_level_threshold(util::Logger::Level::all); + auto db = DB::create(make_in_realm_history(), path, options); + ColKey col; + ColKey col_int; + + { + auto wt = db->start_write(); + + auto t = wt->add_table_with_primary_key("foo", type_Int, "id"); + col = t->add_column(type_Mixed, "any"); + col_int = t->add_column(type_Int, "int"); + auto dict = + t->create_object_with_primary_key(1).set_collection(col, CollectionType::Dictionary).get_dictionary(col); + dict.insert("hello", "world"); + auto list = + t->create_object_with_primary_key(2).set_collection(col, CollectionType::List).get_list(col); + list.add(47.50); + auto set = t->create_object_with_primary_key(3).set_collection(col, CollectionType::Set).get_set(col); + set.insert(false); + wt->commit(); + } + { + // Try to serialize a query with a constraining view + auto rt = db->start_read(); + auto table = rt->get_table("foo"); + TableView tv = table->find_all_int(col_int, 0); + table->where(&tv).equal(col_int, 0).count(); + } + + auto str = buffer.str(); + // std::cout << str << std::endl; + CHECK(str.find("Query::get_description() failed:") != std::string::npos); + CHECK(str.find("Set 'any' to dictionary") != std::string::npos); + CHECK(str.find("Set 'any' to list") != std::string::npos); + CHECK(str.find("Set 'any' to set") != std::string::npos); +} + #endif // TEST_TABLE From 8de64823e8c13272d4ef447121c4082e6d346366 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Wed, 20 Sep 2023 17:45:57 -0400 Subject: [PATCH 28/34] Fix benchmark-common-tasks test failures (#6982) --- test/object-store/CMakeLists.txt | 8 +------- test/object-store/benchmarks/CMakeLists.txt | 3 +-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/test/object-store/CMakeLists.txt b/test/object-store/CMakeLists.txt index e07c4268caa..dbc6613b927 100644 --- a/test/object-store/CMakeLists.txt +++ b/test/object-store/CMakeLists.txt @@ -1,7 +1,4 @@ set(HEADERS - ../util/crypt_key.hpp - ../util/spawned_process.hpp - ../util/test_path.hpp util/event_loop.hpp util/index_helpers.hpp util/test_file.hpp @@ -32,9 +29,6 @@ set(SOURCES c_api/c_api.cpp c_api/c_api.c - ../util/crypt_key.cpp - ../util/spawned_process.cpp - ../util/test_path.cpp util/event_loop.cpp util/test_file.cpp util/test_utils.cpp @@ -93,7 +87,7 @@ if(MSVC) target_compile_options(ObjectStoreTests PRIVATE /bigobj) endif() -target_link_libraries(ObjectStoreTests Catch2::Catch2 ObjectStore RealmFFIStatic) +target_link_libraries(ObjectStoreTests Catch2::Catch2 ObjectStore TestUtil RealmFFIStatic) enable_stdfilesystem(ObjectStoreTests) create_coverage_target(generate-coverage ObjectStoreTests) diff --git a/test/object-store/benchmarks/CMakeLists.txt b/test/object-store/benchmarks/CMakeLists.txt index 14a8283a897..9124d3951f8 100644 --- a/test/object-store/benchmarks/CMakeLists.txt +++ b/test/object-store/benchmarks/CMakeLists.txt @@ -10,7 +10,6 @@ set(SOURCES object.cpp results.cpp - ../../util/crypt_key.cpp ../util/event_loop.cpp ../util/test_file.cpp ../util/test_utils.cpp @@ -39,7 +38,7 @@ if(REALM_ENABLE_SYNC) enable_stdfilesystem(object-store-benchmarks) endif() -target_link_libraries(object-store-benchmarks ObjectStore Catch2::Catch2) +target_link_libraries(object-store-benchmarks ObjectStore TestUtil Catch2::Catch2) set_target_properties(object-store-benchmarks PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1) From 95f4a7c2a5af39453fa7b22fc73ba36f828eaa4b Mon Sep 17 00:00:00 2001 From: James Stone Date: Wed, 20 Sep 2023 15:11:27 -0700 Subject: [PATCH 29/34] Remove core metrics (#6990) * remove core metrics * remove metrics from package.swift --- CHANGELOG.md | 1 + CMakeLists.txt | 1 - Package.swift | 2 - src/realm/CMakeLists.txt | 11 - src/realm/db.cpp | 14 - src/realm/db.hpp | 6 - src/realm/db_options.hpp | 8 - src/realm/group.cpp | 23 - src/realm/group.hpp | 18 - src/realm/group_writer.cpp | 10 - src/realm/metrics/metric_timer.cpp | 144 --- src/realm/metrics/metric_timer.hpp | 100 -- src/realm/metrics/metrics.cpp | 133 --- src/realm/metrics/metrics.hpp | 76 -- src/realm/metrics/query_info.cpp | 127 --- src/realm/metrics/query_info.hpp | 69 -- src/realm/metrics/transaction_info.cpp | 108 --- src/realm/metrics/transaction_info.hpp | 73 -- src/realm/query.cpp | 30 - src/realm/query.hpp | 5 - src/realm/query_engine.hpp | 1 - src/realm/query_expression.hpp | 1 - src/realm/table.hpp | 4 - src/realm/transaction.cpp | 36 - src/realm/util/config.h.in | 1 - test/CMakeLists.txt | 1 - test/test_metrics.cpp | 1207 ------------------------ test/test_parser.cpp | 1 - test/testsettings.hpp | 1 - 29 files changed, 1 insertion(+), 2211 deletions(-) delete mode 100644 src/realm/metrics/metric_timer.cpp delete mode 100644 src/realm/metrics/metric_timer.hpp delete mode 100644 src/realm/metrics/metrics.cpp delete mode 100644 src/realm/metrics/metrics.hpp delete mode 100644 src/realm/metrics/query_info.cpp delete mode 100644 src/realm/metrics/query_info.hpp delete mode 100644 src/realm/metrics/transaction_info.cpp delete mode 100644 src/realm/metrics/transaction_info.hpp delete mode 100644 test/test_metrics.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d80254703d..4bcd5d45c84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ ### Internals * Update History Command tool to work with realms with fileformat v23 ([PR #6970](https://github.com/realm/realm-core/pull/6970)) * Don't edit the ObjectStore target to enable the libuv scheduler in tests, just register the factory instead. ([PR #6699](https://github.com/realm/realm-core/pull/6699)) +* Removed the core metrics which were unused. ([PR 6990](https://github.com/realm/realm-core/pull/6990)) ---------------------------------------------- diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e94e6a0317..131db994b8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,7 +254,6 @@ if(NOT EMSCRIPTEN) endif() option(REALM_ENABLE_MEMDEBUG "Add additional memory checks" OFF) option(REALM_VALGRIND "Tell the test suite we are running with valgrind" OFF) -option(REALM_METRICS "Enable various metric tracking" ON) option(REALM_SYNC_MULTIPLEXING "Enables/disables sync session multiplexing by default" ON) set(REALM_MAX_BPNODE_SIZE "1000" CACHE STRING "Max B+ tree node size.") option(REALM_ENABLE_GEOSPATIAL "Enable geospatial types and queries." ON) diff --git a/Package.swift b/Package.swift index 1bdf234c4cb..816de82e813 100644 --- a/Package.swift +++ b/Package.swift @@ -403,7 +403,6 @@ let package = Package( "realm/CMakeLists.txt", "realm/exec", "realm/geospatial.cpp", - "realm/metrics", "realm/object-store/CMakeLists.txt", "realm/object-store/c_api", "realm/object-store/impl/epoll", @@ -451,7 +450,6 @@ let package = Package( "external", "realm/CMakeLists.txt", "realm/exec", - "realm/metrics", "realm/object-store", "realm/parser", "realm/sync/CMakeLists.txt", diff --git a/src/realm/CMakeLists.txt b/src/realm/CMakeLists.txt index f4751827f94..03ae01356d1 100644 --- a/src/realm/CMakeLists.txt +++ b/src/realm/CMakeLists.txt @@ -249,11 +249,6 @@ set(REALM_INSTALL_HEADERS util/to_string.hpp util/type_traits.hpp util/uri.hpp - - metrics/metrics.hpp - metrics/metric_timer.hpp - metrics/query_info.hpp - metrics/transaction_info.hpp ) # REALM_INSTALL_HEADERS set(REALM_NOINST_HEADERS @@ -279,12 +274,6 @@ set(REALM_NOINST_HEADERS util/value_reset_guard.hpp ) # REALM_NOINST_HEADERS -list(APPEND REALM_SOURCES - metrics/metrics.cpp - metrics/metric_timer.cpp - metrics/query_info.cpp - metrics/transaction_info.cpp) - if(NOT MSVC) list(APPEND REALM_SOURCES util/interprocess_mutex.cpp) endif() diff --git a/src/realm/db.cpp b/src/realm/db.cpp index 3952efc097e..b0178081808 100644 --- a/src/realm/db.cpp +++ b/src/realm/db.cpp @@ -57,7 +57,6 @@ using namespace realm; -using namespace realm::metrics; using namespace realm::util; using Durability = DBOptions::Durability; @@ -1443,11 +1442,6 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions& opt close(); throw; } -#if REALM_METRICS - if (options.enable_metrics) { - m_metrics = std::make_shared(options.metrics_buffer_size); - } -#endif // REALM_METRICS m_alloc.set_read_only(true); } @@ -1534,11 +1528,6 @@ void DB::open(Replication& repl, const DBOptions options) m_file_format_version = target_file_format_version; -#if REALM_METRICS - if (options.enable_metrics) { - m_metrics = std::make_shared(options.metrics_buffer_size); - } -#endif // REALM_METRICS m_info = info; m_alloc.set_read_only(true); } @@ -2483,9 +2472,6 @@ void DB::low_level_commit(uint_fast64_t new_version, Transaction& transaction, b auto live_versions = top_refs.size(); // Do the actual commit REALM_ASSERT(oldest_version <= new_version); -#if REALM_METRICS - transaction.update_num_objects(); -#endif // REALM_METRICS GroupWriter out(transaction, Durability(info->durability), m_marker_observer.get()); // Throws out.set_versions(new_version, top_refs, any_new_unreachables); diff --git a/src/realm/db.hpp b/src/realm/db.hpp index bf8d419b725..c87f293a14c 100644 --- a/src/realm/db.hpp +++ b/src/realm/db.hpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -391,10 +390,6 @@ class DB : public std::enable_shared_from_this { /// On the importing side, the top-level accessor being created during /// import takes ownership of all other accessors (if any) being created as /// part of the import. - std::shared_ptr get_metrics() - { - return m_metrics; - } // Try to grab an exclusive lock of the given realm path's lock file. If the lock // can be acquired, the callback will be executed with the lock and then return true. @@ -507,7 +502,6 @@ class DB : public std::enable_shared_from_this { util::InterprocessCondVar m_new_commit_available; util::InterprocessCondVar m_pick_next_writer; std::function m_upgrade_callback; - std::shared_ptr m_metrics; std::unique_ptr m_commit_helper; std::shared_ptr m_logger; bool m_is_sync_agent = false; diff --git a/src/realm/db_options.hpp b/src/realm/db_options.hpp index 327507a3d20..781e561a96a 100644 --- a/src/realm/db_options.hpp +++ b/src/realm/db_options.hpp @@ -80,14 +80,6 @@ struct DBOptions { /// This string should include a trailing slash '/'. std::string temp_dir = sys_tmp_dir; - /// Controls the feature of collecting various metrics to the DB. - /// A prerequisite is compiling with REALM_METRICS=ON. - bool enable_metrics = false; - - /// The maximum number of entries stored by the metrics (if enabled). If this number - /// is exceeded without being consumed, only the most recent entries will be stored. - size_t metrics_buffer_size = 10000; - /// is_immutable should be set to true if run from a read-only file system. /// this will prevent the DB from making any writes, also disabling the creation /// of write transactions. diff --git a/src/realm/group.cpp b/src/realm/group.cpp index 5b27ab9c291..c792021430f 100644 --- a/src/realm/group.cpp +++ b/src/realm/group.cpp @@ -76,7 +76,6 @@ Group::Group(const std::string& file_path, const char* encryption_key) , m_top(m_alloc) , m_tables(m_alloc) , m_table_names(m_alloc) - , m_total_rows(0) { init_array_parents(); @@ -99,7 +98,6 @@ Group::Group(BinaryData buffer, bool take_ownership) , m_top(m_alloc) , m_tables(m_alloc) , m_table_names(m_alloc) - , m_total_rows(0) { REALM_ASSERT(buffer.data()); @@ -118,7 +116,6 @@ Group::Group(SlabAlloc* alloc) noexcept m_top(m_alloc) , m_tables(m_alloc) , m_table_names(m_alloc) - , m_total_rows(0) { init_array_parents(); } @@ -560,9 +557,6 @@ void Group::attach(ref_type top_ref, bool writable, bool create_group_when_missi while (m_table_accessors.size() < sz) { m_table_accessors.emplace_back(); } -#if REALM_METRICS - update_num_objects(); -#endif // REALM_METRICS } @@ -578,23 +572,6 @@ void Group::detach() noexcept m_attached = false; } -void Group::update_num_objects() -{ -#if REALM_METRICS - if (m_metrics) { - // This is quite invasive and completely defeats the lazy loading mechanism - // where table accessors are only instantiated on demand, because they are all created here. - - m_total_rows = 0; - auto keys = get_table_keys(); - for (auto key : keys) { - ConstTableRef t = get_table(key); - m_total_rows += t->size(); - } - } -#endif // REALM_METRICS -} - void Group::attach_shared(ref_type new_top_ref, size_t new_file_size, bool writable, VersionID version) { REALM_ASSERT_3(new_top_ref, <, new_file_size); diff --git a/src/realm/group.hpp b/src/realm/group.hpp index ae43bfca187..b61bfe06782 100644 --- a/src/realm/group.hpp +++ b/src/realm/group.hpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -615,9 +614,7 @@ class Group : public ArrayParent { util::UniqueFunction m_notify_handler; util::UniqueFunction m_schema_change_handler; - std::shared_ptr m_metrics; std::vector m_objects_to_delete; - size_t m_total_rows; Group(SlabAlloc* alloc) noexcept; void init_array_parents() noexcept; @@ -690,9 +687,6 @@ class Group : public ArrayParent { void write(util::File& file, const char* encryption_key, uint_fast64_t version_number, TableWriter& writer) const; void write(std::ostream&, bool pad, uint_fast64_t version_numer, TableWriter& writer) const; - std::shared_ptr get_metrics() const noexcept; - void set_metrics(std::shared_ptr other) noexcept; - void update_num_objects(); /// Memory mappings must have been updated to reflect any growth in filesize before /// calling advance_transact() void advance_transact(ref_type new_top_ref, util::InputStream*, bool writable); @@ -837,8 +831,6 @@ class Group : public ArrayParent { friend class GroupCommitter; friend class DB; friend class _impl::GroupFriend; - friend class metrics::QueryInfo; - friend class metrics::Metrics; friend class Transaction; friend class TableKeyIterator; friend class CascadeState; @@ -1175,16 +1167,6 @@ inline void Group::reset_free_space_tracking() m_alloc.reset_free_space_tracking(); // Throws } -inline std::shared_ptr Group::get_metrics() const noexcept -{ - return m_metrics; -} - -inline void Group::set_metrics(std::shared_ptr shared) noexcept -{ - m_metrics = shared; -} - // The purpose of this class is to give internal access to some, but // not all of the non-public parts of the Group class. class _impl::GroupFriend { diff --git a/src/realm/group_writer.cpp b/src/realm/group_writer.cpp index 9f7834ab20f..41225a60753 100644 --- a/src/realm/group_writer.cpp +++ b/src/realm/group_writer.cpp @@ -28,13 +28,11 @@ #include #include #include -#include #include #include using namespace realm; using namespace realm::util; -using namespace realm::metrics; namespace realm { class InMemoryWriter : public _impl::ArrayWriterBase { @@ -647,10 +645,6 @@ void GroupWriter::prepare_evacuation() ref_type GroupWriter::write_group() { -#if REALM_METRICS - std::unique_ptr fsync_timer = Metrics::report_write_time(m_group); -#endif // REALM_METRICS - ALLOC_DBG_COUT("Commit nr " << m_current_version << " ( from " << m_oldest_reachable_version << " )" << std::endl); @@ -1409,10 +1403,6 @@ void GroupCommitter::commit(ref_type new_top_ref) bool disable_sync = get_disable_sync_to_disk() || m_durability == Durability::Unsafe; file_header.m_top_ref[slot_selector] = new_top_ref; -#if REALM_METRICS - std::unique_ptr fsync_timer = Metrics::report_fsync_time(m_group); -#endif // REALM_METRICS - // Make sure that that all data relating to the new snapshot is written to // stable storage before flipping the slot selector window->encryption_write_barrier(&file_header, sizeof file_header); diff --git a/src/realm/metrics/metric_timer.cpp b/src/realm/metrics/metric_timer.cpp deleted file mode 100644 index c8ff2d237b8..00000000000 --- a/src/realm/metrics/metric_timer.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/************************************************************************* - * - * Copyright 2016 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. - * - **************************************************************************/ - -#include - -#include -#include -#include -#include - -using namespace realm; -using namespace realm::metrics; - - -MetricTimerResult::MetricTimerResult() - : m_elapsed_nanoseconds(0) -{ -} - -MetricTimerResult::~MetricTimerResult() -{ -} - -nanosecond_storage_t MetricTimerResult::get_elapsed_nanoseconds() const -{ - return m_elapsed_nanoseconds; -} - -void MetricTimerResult::report_nanoseconds(nanosecond_storage_t time) -{ - m_elapsed_nanoseconds = time; -} - - -MetricTimer::MetricTimer(std::shared_ptr destination) - : m_dest(destination) -{ - reset(); -} - -MetricTimer::~MetricTimer() -{ - if (m_dest) { - m_dest->report_nanoseconds(get_elapsed_nanoseconds()); - } -} - -MetricTimer::time_point MetricTimer::get_timer_ticks() const -{ - return clock_type::now(); -} - -nanosecond_storage_t MetricTimer::calc_elapsed_nanoseconds(MetricTimer::time_point begin, - MetricTimer::time_point end) const -{ - std::chrono::duration elapsed = end - begin; - return elapsed.count(); -} - -std::string MetricTimer::format(nanosecond_storage_t nanoseconds) -{ - std::ostringstream out; - format(nanoseconds, out); - return out.str(); -} - -namespace { - -int64_t round_to_int64(double x) -{ - // Note: this is std::llround() from as of c++11, - // but this will actually use a native implementation on android. - return llround(x); -} - -} // end anonymous namespace - -// see also test/util/Timer.cpp -void MetricTimer::format(nanosecond_storage_t nanoseconds, std::ostream& out) -{ - constexpr int64_t ns_per_second = 1'000'000'000; - int64_t rounded_minutes = round_to_int64(nanoseconds / (60.0 * ns_per_second)); - if (rounded_minutes > 60) { - // 1h0m -> inf - int64_t hours = rounded_minutes / 60; - int64_t minutes = rounded_minutes % 60; - out << hours << "h" << minutes << "m"; - } - else { - int64_t rounded_seconds = round_to_int64(nanoseconds / double(ns_per_second)); - if (rounded_seconds > 60) { - // 1m0s -> 59m59s - int64_t minutes = rounded_seconds / 60; - int64_t seconds = rounded_seconds % 60; - out << minutes << "m" << seconds << "s"; - } - else { - int64_t rounded_centies = round_to_int64(nanoseconds / double(10'000'000)); - if (rounded_centies > 100) { - // 1s -> 59.99s - int64_t seconds = rounded_centies / 100; - int64_t centies = rounded_centies % 100; - out << seconds; - if (centies > 0) { - out << '.' << std::setw(2) << std::setfill('0') << centies; - } - out << 's'; - } - else { - int64_t rounded_centi_ms = round_to_int64(nanoseconds / double(10'000)); - if (rounded_centi_ms > 100) { - // 0.1ms -> 999.99ms - int64_t ms = rounded_centi_ms / 100; - int64_t centi_ms = rounded_centi_ms % 100; - out << ms; - if (centi_ms > 0) { - out << '.' << std::setw(2) << std::setfill('0') << centi_ms; - } - out << "ms"; - } - else { - // 0 -> 999µs - int64_t us = round_to_int64(nanoseconds / double(1'000)); - out << us << "us"; - } - } - } - } -} diff --git a/src/realm/metrics/metric_timer.hpp b/src/realm/metrics/metric_timer.hpp deleted file mode 100644 index b80c6f549e6..00000000000 --- a/src/realm/metrics/metric_timer.hpp +++ /dev/null @@ -1,100 +0,0 @@ -/************************************************************************* - * - * Copyright 2016 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. - * - **************************************************************************/ - -#ifndef REALM_METRIC_TIMER_HPP -#define REALM_METRIC_TIMER_HPP - -#include - -#include -#include -#include - -namespace realm { -namespace metrics { - -using nanosecond_storage_t = int64_t; - -class MetricTimerResult { -public: - MetricTimerResult(); - ~MetricTimerResult(); - nanosecond_storage_t get_elapsed_nanoseconds() const; - void report_nanoseconds(nanosecond_storage_t time); - -protected: - nanosecond_storage_t m_elapsed_nanoseconds; -}; - - -class MetricTimer { -public: - MetricTimer(std::shared_ptr destination = nullptr); - ~MetricTimer(); - - void reset(); - - /// Returns elapsed time in nanoseconds since last call to reset(). - nanosecond_storage_t get_elapsed_nanoseconds() const; - /// Same as get_elapsed_time(). - operator nanosecond_storage_t() const; - - /// Format the elapsed time on the form 0h00m, 00m00s, 00.00s, or - /// 000.0ms depending on magnitude. - static void format(nanosecond_storage_t nanoseconds, std::ostream&); - - static std::string format(nanosecond_storage_t nanoseconds); - -private: - using clock_type = std::chrono::high_resolution_clock; - using time_point = std::chrono::time_point; - time_point m_start; - time_point m_paused_at; - std::shared_ptr m_dest; - - time_point get_timer_ticks() const; - nanosecond_storage_t calc_elapsed_nanoseconds(time_point begin, time_point end) const; -}; - - -inline void MetricTimer::reset() -{ - m_start = get_timer_ticks(); -} - -inline nanosecond_storage_t MetricTimer::get_elapsed_nanoseconds() const -{ - return calc_elapsed_nanoseconds(m_start, get_timer_ticks()); -} - -inline MetricTimer::operator nanosecond_storage_t() const -{ - return get_elapsed_nanoseconds(); -} - -inline std::ostream& operator<<(std::ostream& out, const MetricTimer& timer) -{ - MetricTimer::format(timer, out); - return out; -} - - -} // namespace metrics -} // namespace realm - -#endif // REALM_METRIC_TIMER_HPP diff --git a/src/realm/metrics/metrics.cpp b/src/realm/metrics/metrics.cpp deleted file mode 100644 index b33de0af520..00000000000 --- a/src/realm/metrics/metrics.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/************************************************************************* - * - * Copyright 2016 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. - * - **************************************************************************/ - -#include -#include - -using namespace realm; -using namespace realm::metrics; - -Metrics::Metrics(size_t max_history_size) - : m_max_num_queries(max_history_size) - , m_max_num_transactions(max_history_size) -{ - m_query_info = std::make_unique(max_history_size); - m_transaction_info = std::make_unique(max_history_size); -} - -Metrics::~Metrics() noexcept -{ -} - -size_t Metrics::num_query_metrics() const -{ - return m_query_info ? m_query_info->size() : 0; -} - -size_t Metrics::num_transaction_metrics() const -{ - return m_transaction_info ? m_transaction_info->size() : 0; -} - -void Metrics::add_query(QueryInfo info) -{ - REALM_ASSERT_DEBUG(m_query_info); - m_query_info->insert(info); -} - -void Metrics::add_transaction(TransactionInfo info) -{ - REALM_ASSERT_DEBUG(m_transaction_info); - m_transaction_info->insert(info); -} - -void Metrics::start_read_transaction() -{ - REALM_ASSERT_DEBUG(!m_pending_read); - m_pending_read = std::make_unique(TransactionInfo::read_transaction); -} - -void Metrics::start_write_transaction() -{ - REALM_ASSERT_DEBUG(!m_pending_write); - m_pending_write = std::make_unique(TransactionInfo::write_transaction); -} - -void Metrics::end_read_transaction(size_t total_size, size_t free_space, size_t num_objects, size_t num_versions, - size_t num_decrypted_pages) -{ - REALM_ASSERT_DEBUG(m_transaction_info); - if (m_pending_read) { - m_pending_read->update_stats(total_size, free_space, num_objects, num_versions, num_decrypted_pages); - m_pending_read->finish_timer(); - add_transaction(*m_pending_read); - m_pending_read.reset(nullptr); - } -} - -void Metrics::end_write_transaction(size_t total_size, size_t free_space, size_t num_objects, size_t num_versions, - size_t num_decrypted_pages) -{ - REALM_ASSERT_DEBUG(m_transaction_info); - if (m_pending_write) { - m_pending_write->update_stats(total_size, free_space, num_objects, num_versions, num_decrypted_pages); - m_pending_write->finish_timer(); - add_transaction(*m_pending_write); - m_pending_write.reset(nullptr); - } -} - -std::unique_ptr Metrics::report_fsync_time(const Group& g) -{ - std::shared_ptr instance = g.get_metrics(); - if (instance) { - REALM_ASSERT_DEBUG(instance->m_transaction_info); - if (instance->m_pending_write) { - return std::make_unique(instance->m_pending_write->m_fsync_time); - } - } - return nullptr; -} - -std::unique_ptr Metrics::report_write_time(const Group& g) -{ - std::shared_ptr instance = g.get_metrics(); - if (instance) { - REALM_ASSERT_DEBUG(instance->m_transaction_info); - if (instance->m_pending_write) { - return std::make_unique(instance->m_pending_write->m_write_time); - } - } - return nullptr; -} - - -std::unique_ptr Metrics::take_queries() -{ - - std::unique_ptr values = std::make_unique(m_max_num_queries); - values.swap(m_query_info); - return values; -} - -std::unique_ptr Metrics::take_transactions() -{ - std::unique_ptr values = std::make_unique(m_max_num_transactions); - values.swap(m_transaction_info); - return values; -} diff --git a/src/realm/metrics/metrics.hpp b/src/realm/metrics/metrics.hpp deleted file mode 100644 index 3e9ff00a957..00000000000 --- a/src/realm/metrics/metrics.hpp +++ /dev/null @@ -1,76 +0,0 @@ -/************************************************************************* - * - * Copyright 2016 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. - * - **************************************************************************/ - -#ifndef REALM_METRICS_HPP -#define REALM_METRICS_HPP - -#include - -#include -#include -#include -#include "realm/util/fixed_size_buffer.hpp" - -namespace realm { - -class Group; - -namespace metrics { - -class Metrics { -public: - Metrics(size_t max_history_size); - ~Metrics() noexcept; - size_t num_query_metrics() const; - size_t num_transaction_metrics() const; - - void add_query(QueryInfo info); - void add_transaction(TransactionInfo info); - - void start_read_transaction(); - void start_write_transaction(); - void end_read_transaction(size_t total_size, size_t free_space, size_t num_objects, size_t num_versions, - size_t num_decrypted_pages); - void end_write_transaction(size_t total_size, size_t free_space, size_t num_objects, size_t num_versions, - size_t num_decrypted_pages); - static std::unique_ptr report_fsync_time(const Group& g); - static std::unique_ptr report_write_time(const Group& g); - - using QueryInfoList = util::FixedSizeBuffer; - using TransactionInfoList = util::FixedSizeBuffer; - - // Get the list of metric objects tracked since the last take - std::unique_ptr take_queries(); - std::unique_ptr take_transactions(); - -private: - std::unique_ptr m_query_info; - std::unique_ptr m_transaction_info; - - std::unique_ptr m_pending_read; - std::unique_ptr m_pending_write; - - size_t m_max_num_queries; - size_t m_max_num_transactions; -}; - -} // namespace metrics -} // namespace realm - - -#endif // REALM_METRICS_HPP diff --git a/src/realm/metrics/query_info.cpp b/src/realm/metrics/query_info.cpp deleted file mode 100644 index 4ff00370ead..00000000000 --- a/src/realm/metrics/query_info.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/************************************************************************* - * - * Copyright 2016 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. - * - **************************************************************************/ - -#include -#include -#include -#include - -using namespace realm; -using namespace realm::metrics; - -QueryInfo::QueryInfo(const Query* query, QueryType type) - : m_type(type) -{ -#if REALM_METRICS - REALM_ASSERT(query); - - const Group* group = query->m_table->get_parent_group(); - REALM_ASSERT(group); - - try { - m_description = query->get_description(); - } - catch (const SerializationError& e) { - m_description = e.what(); - } - m_table_name = query->m_table->get_name(); -#else - static_cast(query); -#endif -} - -QueryInfo::~QueryInfo() noexcept -{ -} - -std::string QueryInfo::get_description() const -{ - return m_description; -} - -std::string QueryInfo::get_table_name() const -{ - return m_table_name; -} - -QueryInfo::QueryType QueryInfo::get_type() const -{ - return m_type; -} - -nanosecond_storage_t QueryInfo::get_query_time_nanoseconds() const -{ - if (m_query_time) { - return m_query_time->get_elapsed_nanoseconds(); - } - return 0; -} - -std::unique_ptr QueryInfo::track(const Query* query, QueryType type) -{ -#if REALM_METRICS - REALM_ASSERT_DEBUG(query); - - if (!bool(query->m_table)) { - return nullptr; - } - - const Group* group = query->m_table->get_parent_group(); - - // If the table is not attached to a group we cannot track it's metrics. - if (!group) - return nullptr; - - std::shared_ptr metrics = group->get_metrics(); - if (!metrics) - return nullptr; - - QueryInfo info(query, type); - info.m_query_time = std::make_shared(); - metrics->add_query(info); - - return std::make_unique(info.m_query_time); -#else - static_cast(query); - static_cast(type); - return nullptr; -#endif -} - -QueryInfo::QueryType QueryInfo::type_from_action(Action action) -{ - switch (action) { - case act_ReturnFirst: - return type_Find; - case act_Sum: - return type_Sum; - case act_Max: - return type_Maximum; - case act_Min: - return type_Minimum; - case act_Average: - return type_Average; - case act_Count: - return type_Count; - case act_FindAll: - return type_FindAll; - case act_CallbackIdx: - return type_Invalid; - }; - REALM_UNREACHABLE(); -} diff --git a/src/realm/metrics/query_info.hpp b/src/realm/metrics/query_info.hpp deleted file mode 100644 index 1334a076880..00000000000 --- a/src/realm/metrics/query_info.hpp +++ /dev/null @@ -1,69 +0,0 @@ -/************************************************************************* - * - * Copyright 2016 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. - * - **************************************************************************/ - -#ifndef REALM_QUERY_INFO_HPP -#define REALM_QUERY_INFO_HPP - -#include -#include - -#include -#include -#include - -namespace realm { - -class Query; // forward declare in namespace realm - -namespace metrics { - -class QueryInfo { -public: - enum QueryType { - type_Find, - type_FindAll, - type_Count, - type_Sum, - type_Average, - type_Maximum, - type_Minimum, - type_Invalid - }; - - QueryInfo(const Query* query, QueryType type); - ~QueryInfo() noexcept; - - std::string get_description() const; - std::string get_table_name() const; - QueryType get_type() const; - nanosecond_storage_t get_query_time_nanoseconds() const; - - static std::unique_ptr track(const Query* query, QueryType type); - static QueryType type_from_action(Action action); - -private: - std::string m_description; - std::string m_table_name; - QueryType m_type; - std::shared_ptr m_query_time; -}; - -} // namespace metrics -} // namespace realm - -#endif // REALM_QUERY_INFO_HPP diff --git a/src/realm/metrics/transaction_info.cpp b/src/realm/metrics/transaction_info.cpp deleted file mode 100644 index 6221a583eb0..00000000000 --- a/src/realm/metrics/transaction_info.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/************************************************************************* - * - * Copyright 2016 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. - * - **************************************************************************/ - -#include - -using namespace realm; -using namespace metrics; - -TransactionInfo::TransactionInfo(TransactionInfo::TransactionType type) - : m_realm_disk_size(0) - , m_realm_free_space(0) - , m_total_objects(0) - , m_type(type) - , m_num_versions(0) - , m_num_decrypted_pages(0) -{ -#if REALM_METRICS - if (m_type == write_transaction) { - m_fsync_time = std::make_shared(); - m_write_time = std::make_shared(); - } -#endif -} - -TransactionInfo::~TransactionInfo() noexcept -{ -} - -TransactionInfo::TransactionType TransactionInfo::get_transaction_type() const -{ - return m_type; -} - -nanosecond_storage_t TransactionInfo::get_transaction_time_nanoseconds() const -{ - return m_transaction_time.get_elapsed_nanoseconds(); -} - -nanosecond_storage_t TransactionInfo::get_fsync_time_nanoseconds() const -{ - if (m_fsync_time) { - return m_fsync_time->get_elapsed_nanoseconds(); - } - return 0; -} - -nanosecond_storage_t TransactionInfo::get_write_time_nanoseconds() const -{ - if (m_write_time) { - return m_write_time->get_elapsed_nanoseconds(); - } - return 0; -} - -size_t TransactionInfo::get_disk_size() const -{ - return m_realm_disk_size; -} - -size_t TransactionInfo::get_free_space() const -{ - return m_realm_free_space; -} - -size_t TransactionInfo::get_total_objects() const -{ - return m_total_objects; -} - -size_t TransactionInfo::get_num_available_versions() const -{ - return m_num_versions; -} - -size_t TransactionInfo::get_num_decrypted_pages() const -{ - return m_num_decrypted_pages; -} - -void TransactionInfo::update_stats(size_t disk_size, size_t free_space, size_t total_objects, - size_t available_versions, size_t num_decrypted_pages) -{ - m_realm_disk_size = disk_size; - m_realm_free_space = free_space; - m_total_objects = total_objects; - m_num_versions = available_versions; - m_num_decrypted_pages = num_decrypted_pages; -} - -void TransactionInfo::finish_timer() -{ - m_transaction_time.report_nanoseconds(m_transact_timer.get_elapsed_nanoseconds()); -} diff --git a/src/realm/metrics/transaction_info.hpp b/src/realm/metrics/transaction_info.hpp deleted file mode 100644 index 2706024a192..00000000000 --- a/src/realm/metrics/transaction_info.hpp +++ /dev/null @@ -1,73 +0,0 @@ -/************************************************************************* - * - * Copyright 2016 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. - * - **************************************************************************/ - -#ifndef REALM_TRANSACTION_INFO_HPP -#define REALM_TRANSACTION_INFO_HPP - -#include -#include - -#include -#include - -namespace realm { -namespace metrics { - -class Metrics; - -class TransactionInfo { -public: - enum TransactionType { read_transaction, write_transaction }; - TransactionInfo(TransactionType type); - TransactionInfo(const TransactionInfo&) = default; - ~TransactionInfo() noexcept; - - TransactionType get_transaction_type() const; - // the transaction time is a total amount which includes fsync_time + write_time + user_time - nanosecond_storage_t get_transaction_time_nanoseconds() const; - nanosecond_storage_t get_fsync_time_nanoseconds() const; - nanosecond_storage_t get_write_time_nanoseconds() const; - size_t get_disk_size() const; - size_t get_free_space() const; - size_t get_total_objects() const; - size_t get_num_available_versions() const; - size_t get_num_decrypted_pages() const; - -private: - MetricTimerResult m_transaction_time; - std::shared_ptr m_fsync_time; - std::shared_ptr m_write_time; - MetricTimer m_transact_timer; - - size_t m_realm_disk_size; - size_t m_realm_free_space; - size_t m_total_objects; - TransactionType m_type; - size_t m_num_versions; - size_t m_num_decrypted_pages; - - friend class Metrics; - void update_stats(size_t disk_size, size_t free_space, size_t total_objects, size_t available_versions, - size_t num_decrypted_pages); - void finish_timer(); -}; - -} // namespace metrics -} // namespace realm - -#endif // REALM_TRANSACTION_INFO_HPP diff --git a/src/realm/query.cpp b/src/realm/query.cpp index 029010f3290..84a43189919 100644 --- a/src/realm/query.cpp +++ b/src/realm/query.cpp @@ -31,7 +31,6 @@ #include using namespace realm; -using namespace realm::metrics; Query::Query() { @@ -1105,33 +1104,21 @@ void Query::aggregate_internal(ParentNode* pn, QueryStateBase* st, size_t start, std::optional Query::sum(ColKey col_key) const { -#if REALM_METRICS - auto metric_timer = QueryInfo::track(this, QueryInfo::type_Sum); -#endif return AggregateHelper::sum(*m_table, *this, col_key); } std::optional Query::avg(ColKey col_key, size_t* value_count) const { -#if REALM_METRICS - auto metric_timer = QueryInfo::track(this, QueryInfo::type_Average); -#endif return AggregateHelper::avg(*m_table, *this, col_key, value_count); } std::optional Query::min(ColKey col_key, ObjKey* return_ndx) const { -#if REALM_METRICS - auto metric_timer = QueryInfo::track(this, QueryInfo::type_Minimum); -#endif return AggregateHelper::min(*m_table, *this, col_key, return_ndx); } std::optional Query::max(ColKey col_key, ObjKey* return_ndx) const { -#if REALM_METRICS - auto metric_timer = QueryInfo::track(this, QueryInfo::type_Maximum); -#endif return AggregateHelper::max(*m_table, *this, col_key, return_ndx); } @@ -1207,10 +1194,6 @@ ObjKey Query::find() const if (!m_table) return ret; -#if REALM_METRICS - std::unique_ptr metric_timer = QueryInfo::track(this, QueryInfo::type_Find); -#endif - auto logger = m_table->get_logger(); bool do_log = false; std::chrono::steady_clock::time_point t1; @@ -1392,10 +1375,6 @@ void Query::do_find_all(QueryStateBase& st) const TableView Query::find_all(size_t limit) const { -#if REALM_METRICS - std::unique_ptr metric_timer = QueryInfo::track(this, QueryInfo::type_FindAll); -#endif - TableView ret(*this, limit); if (m_ordering) { // apply_descriptor_ordering will call do_sync @@ -1515,9 +1494,6 @@ size_t Query::do_count(size_t limit) const size_t Query::count() const { -#if REALM_METRICS - std::unique_ptr metric_timer = QueryInfo::track(this, QueryInfo::type_Count); -#endif if (!m_table) return 0; return do_count(); @@ -1525,9 +1501,6 @@ size_t Query::count() const TableView Query::find_all(const DescriptorOrdering& descriptor) const { -#if REALM_METRICS - std::unique_ptr metric_timer = QueryInfo::track(this, QueryInfo::type_FindAll); -#endif if (descriptor.is_empty()) { return find_all(); } @@ -1558,9 +1531,6 @@ TableView Query::find_all(const DescriptorOrdering& descriptor) const size_t Query::count(const DescriptorOrdering& descriptor) const { -#if REALM_METRICS - std::unique_ptr metric_timer = QueryInfo::track(this, QueryInfo::type_Count); -#endif if (!m_table) return 0; realm::util::Optional min_limit = descriptor.get_min_limit(); diff --git a/src/realm/query.hpp b/src/realm/query.hpp index 0ed4330c7e9..cb89b22c2f6 100644 --- a/src/realm/query.hpp +++ b/src/realm/query.hpp @@ -57,10 +57,6 @@ class TableView; class Timestamp; class Transaction; -namespace metrics { -class QueryInfo; -} - struct QueryGroup { enum class State { Default, @@ -379,7 +375,6 @@ class Query final { friend class TableView; friend class SubQueryCount; friend class PrimitiveListCount; - friend class metrics::QueryInfo; template friend class AggregateHelper; diff --git a/src/realm/query_engine.hpp b/src/realm/query_engine.hpp index 33e0dcb75ef..998132eeea5 100644 --- a/src/realm/query_engine.hpp +++ b/src/realm/query_engine.hpp @@ -85,7 +85,6 @@ TConditionValue: Type of values in condition column. That is, int64_t, float, #include #include #include -#include #include #include #include diff --git a/src/realm/query_expression.hpp b/src/realm/query_expression.hpp index 6832c4a868e..b1e70c65123 100644 --- a/src/realm/query_expression.hpp +++ b/src/realm/query_expression.hpp @@ -148,7 +148,6 @@ The Columns class encapsulates all this into a simple class that, for any type T #include #include #include -#include #include #include diff --git a/src/realm/table.hpp b/src/realm/table.hpp index a8dfc50d490..769ea29959b 100644 --- a/src/realm/table.hpp +++ b/src/realm/table.hpp @@ -73,9 +73,6 @@ class TableFriend; namespace util { class Logger; } -namespace metrics { -class QueryInfo; -} namespace query_parser { class Arguments; class KeyPathMapping; @@ -851,7 +848,6 @@ class Table { friend class _impl::TableFriend; friend class Query; - friend class metrics::QueryInfo; template friend class SimpleQuerySupport; friend class TableView; diff --git a/src/realm/transaction.cpp b/src/realm/transaction.cpp index f45cf438951..0cefaba0b91 100644 --- a/src/realm/transaction.cpp +++ b/src/realm/transaction.cpp @@ -118,7 +118,6 @@ Transaction::Transaction(DBRef _db, SlabAlloc* alloc, DB::ReadLockInfo& rli, DB: { bool writable = stage == DB::transact_Writing; m_transact_stage = DB::transact_Ready; - set_metrics(db->m_metrics); set_transact_stage(stage); m_alloc.note_reader_start(this); attach_shared(m_read_lock.m_top_ref, m_read_lock.m_file_size, writable, @@ -928,41 +927,6 @@ void Transaction::initialize_replication() void Transaction::set_transact_stage(DB::TransactStage stage) noexcept { -#if REALM_METRICS - REALM_ASSERT(m_metrics == db->m_metrics); - if (m_metrics) { // null if metrics are disabled - size_t free_space; - size_t used_space; - db->get_stats(free_space, used_space); - size_t total_size = used_space + free_space; - - size_t num_objects = m_total_rows; - size_t num_available_versions = static_cast(db->get_number_of_versions()); - size_t num_decrypted_pages = realm::util::get_num_decrypted_pages(); - - if (stage == DB::transact_Reading) { - if (m_transact_stage == DB::transact_Writing) { - m_metrics->end_write_transaction(total_size, free_space, num_objects, num_available_versions, - num_decrypted_pages); - } - m_metrics->start_read_transaction(); - } - else if (stage == DB::transact_Writing) { - if (m_transact_stage == DB::transact_Reading) { - m_metrics->end_read_transaction(total_size, free_space, num_objects, num_available_versions, - num_decrypted_pages); - } - m_metrics->start_write_transaction(); - } - else if (stage == DB::transact_Ready) { - m_metrics->end_read_transaction(total_size, free_space, num_objects, num_available_versions, - num_decrypted_pages); - m_metrics->end_write_transaction(total_size, free_space, num_objects, num_available_versions, - num_decrypted_pages); - } - } -#endif - m_transact_stage = stage; } diff --git a/src/realm/util/config.h.in b/src/realm/util/config.h.in index 8b423306e55..c9efb97cb97 100644 --- a/src/realm/util/config.h.in +++ b/src/realm/util/config.h.in @@ -13,6 +13,5 @@ #cmakedefine01 REALM_ENABLE_MEMDEBUG #cmakedefine01 REALM_ENABLE_GEOSPATIAL #cmakedefine01 REALM_VALGRIND -#cmakedefine01 REALM_METRICS #cmakedefine01 REALM_ASAN #cmakedefine01 REALM_TSAN diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a0ab739161f..7e524e298b0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -53,7 +53,6 @@ set(CORE_TEST_SOURCES test_json.cpp test_link_query_view.cpp test_links.cpp - test_metrics.cpp test_mixed_null_assertions.cpp test_object_id.cpp test_optional.cpp diff --git a/test/test_metrics.cpp b/test/test_metrics.cpp deleted file mode 100644 index 7fea9f9b511..00000000000 --- a/test/test_metrics.cpp +++ /dev/null @@ -1,1207 +0,0 @@ -/************************************************************************* - * - * Copyright 2016 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. - * - **************************************************************************/ - -#include "testsettings.hpp" - -#if defined(TEST_METRICS) -#include "test.hpp" - - -// Test independence and thread-safety -// ----------------------------------- -// -// All tests must be thread safe and independent of each other. This -// is required because it allows for both shuffling of the execution -// order and for parallelized testing. -// -// In particular, avoid using std::rand() since it is not guaranteed -// to be thread safe. Instead use the API offered in -// `test/util/random.hpp`. -// -// All files created in tests must use the TEST_PATH macro (or one of -// its friends) to obtain a suitable file system path. See -// `test/util/test_path.hpp`. -// -// -// Debugging and the ONLY() macro -// ------------------------------ -// -// A simple way of disabling all tests except one called `Foo`, is to -// replace TEST(Foo) with ONLY(Foo) and then recompile and rerun the -// test suite. Note that you can also use filtering by setting the -// environment varible `UNITTEST_FILTER`. See `README.md` for more on -// this. -// -// Another way to debug a particular test, is to copy that test into -// `experiments/testcase.cpp` and then run `sh build.sh -// check-testcase` (or one of its friends) from the command line. - -#include -#include - -using namespace realm; -using namespace realm::metrics; -using namespace realm::test_util; -using namespace realm::util; - -#if REALM_METRICS - -#include -#include -#include - -#include -#include -#include -#include -#include - - -TEST(Metrics_HasNoReportsWhenDisabled) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = false; - DBRef sg = DB::create(*hist, path, options); - CHECK(!sg->get_metrics()); - auto wt = sg->start_write(); - auto table = wt->add_table("table"); - auto col = table->add_column(type_Int, "first"); - std::vector keys; - table->create_objects(10, keys); - wt->commit(); - auto rt = sg->start_read(); - table = rt->get_table("table"); - CHECK(bool(table)); - Query query = table->column(col) == 0; - query.count(); - rt->end_read(); - CHECK(!sg->get_metrics()); -} - -TEST(Metrics_HasReportsWhenEnabled) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - DBRef sg = DB::create(*hist, path, options); - CHECK(sg->get_metrics()); - auto wt = sg->start_write(); - auto table = wt->add_table("table"); - auto col = table->add_column(type_Int, "first"); - std::vector keys; - table->create_objects(10, keys); - wt->commit(); - auto rt = sg->start_read(); - table = rt->get_table("table"); - CHECK(bool(table)); - Query query = table->column(col) == 0; - query.count(); - rt->end_read(); - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - CHECK(metrics->num_query_metrics() != 0); - CHECK(metrics->num_transaction_metrics() != 0); -} - -TEST(Metrics_QueryTypes) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - DBRef sg = DB::create(*hist, path, options); - CHECK(sg->get_metrics()); - auto wt = sg->start_write(); - auto table = wt->add_table("table"); - auto int_col = table->add_column(type_Int, "col_int"); - auto double_col = table->add_column(type_Double, "col_double"); - auto float_col = table->add_column(type_Float, "col_float"); - auto timestamp_col = table->add_column(type_Timestamp, "col_timestamp"); - std::vector keys; - table->create_objects(10, keys); - wt->commit(); - auto rt = sg->start_read(); - table = rt->get_table("table"); - CHECK(bool(table)); - Query query = table->column(int_col) == 0; - query.find(); - query.find_all(); - query.count(); - query.sum(int_col); - query.avg(int_col); - query.max(int_col); - query.min(int_col); - - query.sum(double_col); - query.avg(double_col); - query.max(double_col); - query.min(double_col); - - query.sum(float_col); - query.avg(float_col); - query.max(float_col); - query.min(float_col); - - ObjKey return_dummy; - query.max(timestamp_col, &return_dummy); - query.min(timestamp_col, &return_dummy); - - rt->end_read(); - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - CHECK_EQUAL(metrics->num_query_metrics(), 17); - std::unique_ptr queries = metrics->take_queries(); - CHECK_EQUAL(metrics->num_query_metrics(), 0); - CHECK(queries); - CHECK_EQUAL(queries->size(), 17); - CHECK_EQUAL(queries->at(0).get_type(), QueryInfo::type_Find); - CHECK_EQUAL(queries->at(1).get_type(), QueryInfo::type_FindAll); - CHECK_EQUAL(queries->at(2).get_type(), QueryInfo::type_Count); - CHECK_EQUAL(queries->at(3).get_type(), QueryInfo::type_Sum); - CHECK_EQUAL(queries->at(4).get_type(), QueryInfo::type_Average); - CHECK_EQUAL(queries->at(5).get_type(), QueryInfo::type_Maximum); - CHECK_EQUAL(queries->at(6).get_type(), QueryInfo::type_Minimum); - - CHECK_EQUAL(queries->at(7).get_type(), QueryInfo::type_Sum); - CHECK_EQUAL(queries->at(8).get_type(), QueryInfo::type_Average); - CHECK_EQUAL(queries->at(9).get_type(), QueryInfo::type_Maximum); - CHECK_EQUAL(queries->at(10).get_type(), QueryInfo::type_Minimum); - - CHECK_EQUAL(queries->at(11).get_type(), QueryInfo::type_Sum); - CHECK_EQUAL(queries->at(12).get_type(), QueryInfo::type_Average); - CHECK_EQUAL(queries->at(13).get_type(), QueryInfo::type_Maximum); - CHECK_EQUAL(queries->at(14).get_type(), QueryInfo::type_Minimum); - - CHECK_EQUAL(queries->at(15).get_type(), QueryInfo::type_Maximum); - CHECK_EQUAL(queries->at(16).get_type(), QueryInfo::type_Minimum); -} - -static size_t find_count(std::string haystack, std::string needle) -{ - size_t find_pos = 0; - size_t count = 0; - while (find_pos < haystack.size()) { - find_pos = haystack.find(needle, find_pos); - if (find_pos == std::string::npos) - break; - ++find_pos; - ++count; - } - return count; -} - -static void populate(DBRef sg) -{ - auto wt = sg->start_write(); - auto person = wt->add_table("person"); - auto pet = wt->add_table("pet"); - person->add_column(type_Int, "age"); - person->add_column(type_Double, "paid"); - person->add_column(type_Float, "weight"); - person->add_column(type_Timestamp, "date_of_birth"); - person->add_column(type_String, "name"); - person->add_column(type_Bool, "account_overdue"); - person->add_column(type_Binary, "data"); - auto owes_col = person->add_column_list(*person, "owes_coffee_to"); - - auto create_person = [&](int age, double paid, float weight, Timestamp dob, std::string name, bool overdue, - std::string data, std::vector owes_coffee_to) { - BinaryData bd(data); - Obj obj = person->create_object().set_all(age, paid, weight, dob, name, overdue, bd); - auto ll = obj.get_linklist(owes_col); - for (auto key : owes_coffee_to) { - ll.add(key); - } - return obj.get_key(); - }; - - auto k0 = create_person(27, 28.80, 170.7f, Timestamp(27, 5), "Bob", true, "e72s", {}); - auto k1 = create_person(28, 10.70, 165.8f, Timestamp(28, 8), "Ryan", false, "s83f", {k0}); - auto k2 = create_person(33, 55.28, 183.3f, Timestamp(33, 3), "Cole", true, "s822k", {k1, k0}); - auto k3 = create_person(39, 22.72, 173.8f, Timestamp(39, 2), "Nathan", true, "h282l", {k1, k1, k0, k2}); - create_person(33, 29.28, 188.7f, Timestamp(33, 9), "Riley", false, "a208s", {k3, k3, k2, k1}); - - pet->add_column(type_String, "name"); - pet->add_column(*person, "owner"); - - auto create_pet = [&](std::string name, ObjKey owner) { - pet->create_object().set_all(name, owner); - }; - - create_pet("Fido", k0); - create_pet("Max", k1); - create_pet("Buddy", k2); - create_pet("Rocky", k3); - create_pet("Toby", k3); - create_pet("Duke", k0); - - wt->commit(); -} - -TEST(Metrics_QueryEqual) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - DBRef sg = DB::create(*hist, path, options); - populate(sg); - - std::string person_table_name = "person"; - std::string pet_table_name = "pet"; - std::string query_search_term = "=="; - - auto wt = sg->start_write(); - TableRef person = wt->get_table(person_table_name); - TableRef pet = wt->get_table(pet_table_name); - CHECK(bool(person)); - - CHECK_EQUAL(person->get_column_count(), 8); - std::vector column_names; - for (auto col : person->get_column_keys()) { - column_names.push_back(person->get_column_name(col)); - } - for (auto col : pet->get_column_keys()) { - column_names.push_back(pet->get_column_name(col)); - } - - Obj p0 = person->get_object(0); - - auto col_age = person->get_column_key("age"); - auto col_paid = person->get_column_key("paid"); - auto col_weight = person->get_column_key("weight"); - auto col_birth = person->get_column_key("date_of_birth"); - auto col_name = person->get_column_key("name"); - auto col_overdue = person->get_column_key("account_overdue"); - auto col_data = person->get_column_key("data"); - auto col_owes = person->get_column_key("owes_coffee_to"); - - auto col_pet_name = pet->get_column_key("name"); - auto col_owner = pet->get_column_key("owner"); - - Query q0 = person->column(col_age) == 0; - Query q1 = person->column(col_paid) == 0.0; - Query q2 = person->column(col_weight) == 0.0f; - Query q3 = person->column(col_birth) == Timestamp(0, 0); - StringData name(""); - Query q4 = person->column(col_name) == name; - Query q5 = person->column(col_overdue) == false; - BinaryData bd(""); - Query q6 = person->column(col_data) == bd; - Query q7 = person->column(col_owes) == p0; - Query q8 = pet->column(col_pet_name) == name; - Query q9 = pet->column(col_owner) == p0; - - q0.find_all(); - q1.find_all(); - q2.find_all(); - q3.find_all(); - q4.find_all(); - q5.find_all(); - q6.find_all(); - q7.find_all(); - q8.find_all(); - q9.find_all(); - - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - std::unique_ptr queries = metrics->take_queries(); - CHECK(queries); - CHECK_EQUAL(queries->size(), 10); - - for (size_t i = 0; i < 10; ++i) { - std::string description = queries->at(i).get_description(); - std::string table_name = queries->at(i).get_table_name(); - CHECK_EQUAL(find_count(description, column_names[i]), 1); - CHECK_GREATER_EQUAL(find_count(description, query_search_term), 1); - if (i < 8) { - CHECK_EQUAL(table_name, "person"); - } - else { - CHECK_EQUAL(table_name, "pet"); - } - } -} - -TEST(Metrics_QueryOrAndNot) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - DBRef sg = DB::create(*hist, path, options); - populate(sg); - - std::string person_table_name = "person"; - std::string pet_table_name = "pet"; - std::string query_search_term = "=="; - std::string not_text = "!"; - - auto wt = sg->start_write(); - TableRef person = wt->get_table(person_table_name); - wt->get_table(pet_table_name); - CHECK(bool(person)); - - CHECK_EQUAL(person->get_column_count(), 8); - std::vector column_names; - for (auto col : person->get_column_keys()) { - column_names.push_back(person->get_column_name(col)); - } - - auto col_age = person->get_column_key("age"); - auto col_paid = person->get_column_key("paid"); - auto col_weight = person->get_column_key("weight"); - Query q0 = person->column(col_age) == 0; - Query q1 = person->column(col_paid) == 0.0; - Query q2 = person->column(col_weight) == 0.1f; - - Query simple_and = q0 && q1; - Query simple_or = q0 || q1; - Query simple_not = !q0; - - Query or_and = q2 || (simple_and); - Query and_or = simple_and || q2; - Query or_nested = q2 || simple_or; - Query and_nested = q2 && simple_and; - Query not_simple_and = !(simple_and); - Query not_simple_or = !(simple_or); - Query not_or_and = !(or_and); - Query not_and_or = !(and_or); - Query not_or_nested = !(or_nested); - Query not_and_nested = !(and_nested); - Query and_true = q0 && std::unique_ptr(new TrueExpression); - Query and_false = q0 && std::unique_ptr(new FalseExpression); - - simple_and.find_all(); - simple_or.find_all(); - simple_not.find_all(); - or_and.find_all(); - and_or.find_all(); - or_nested.find_all(); - and_nested.find_all(); - not_simple_and.find_all(); - not_simple_or.find_all(); - not_or_and.find_all(); - not_and_or.find_all(); - not_or_nested.find_all(); - not_and_nested.find_all(); - and_true.find_all(); - and_false.find_all(); - - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - std::unique_ptr queries = metrics->take_queries(); - CHECK(queries); - CHECK_EQUAL(queries->size(), 15); - - std::string and_description = queries->at(0).get_description(); - CHECK_EQUAL(find_count(and_description, " and "), 1); - CHECK_EQUAL(find_count(and_description, column_names[0]), 1); - CHECK_EQUAL(find_count(and_description, column_names[1]), 1); - CHECK_EQUAL(find_count(and_description, query_search_term), 2); - - std::string or_description = queries->at(1).get_description(); - CHECK_EQUAL(find_count(or_description, " or "), 1); - CHECK_EQUAL(find_count(or_description, column_names[0]), 1); - CHECK_EQUAL(find_count(or_description, column_names[1]), 1); - CHECK_EQUAL(find_count(or_description, query_search_term), 2); - - std::string not_description = queries->at(2).get_description(); - CHECK_EQUAL(find_count(not_description, not_text), 1); - CHECK_EQUAL(find_count(not_description, column_names[0]), 1); - CHECK_EQUAL(find_count(not_description, query_search_term), 1); - - std::string or_and_description = queries->at(3).get_description(); - CHECK_EQUAL(find_count(or_and_description, and_description), 1); - CHECK_EQUAL(find_count(or_and_description, " or "), 1); - CHECK_EQUAL(find_count(or_and_description, column_names[2]), 1); - - std::string and_or_description = queries->at(4).get_description(); - CHECK_EQUAL(find_count(and_or_description, and_description), 1); - CHECK_EQUAL(find_count(and_or_description, " or "), 1); - CHECK_EQUAL(find_count(and_or_description, column_names[2]), 1); - - std::string or_nested_description = queries->at(5).get_description(); - CHECK_EQUAL(find_count(or_nested_description, or_description), 1); - CHECK_EQUAL(find_count(or_nested_description, " or "), 2); - CHECK_EQUAL(find_count(or_nested_description, column_names[2]), 1); - - std::string and_nested_description = queries->at(6).get_description(); - CHECK_EQUAL(find_count(and_nested_description, and_description), 1); - CHECK_EQUAL(find_count(and_nested_description, " and "), 2); - CHECK_EQUAL(find_count(and_nested_description, column_names[2]), 1); - - std::string not_simple_and_description = queries->at(7).get_description(); - CHECK_EQUAL(find_count(not_simple_and_description, and_description), 1); - CHECK_EQUAL(find_count(not_simple_and_description, not_text), 1); - - std::string not_simple_or_description = queries->at(8).get_description(); - CHECK_EQUAL(find_count(not_simple_or_description, or_description), 1); - CHECK_EQUAL(find_count(not_simple_or_description, not_text), 1); - - std::string not_or_and_description = queries->at(9).get_description(); - CHECK_EQUAL(find_count(not_or_and_description, or_and_description), 1); - CHECK_EQUAL(find_count(not_or_and_description, not_text), 1); - - std::string not_and_or_description = queries->at(10).get_description(); - CHECK_EQUAL(find_count(not_and_or_description, and_or_description), 1); - CHECK_EQUAL(find_count(not_and_or_description, not_text), 1); - - std::string not_or_nested_description = queries->at(11).get_description(); - CHECK_EQUAL(find_count(not_or_nested_description, or_nested_description), 1); - CHECK_EQUAL(find_count(not_or_nested_description, not_text), 1); - - std::string not_and_nested_description = queries->at(12).get_description(); - CHECK_EQUAL(find_count(not_and_nested_description, and_nested_description), 1); - CHECK_EQUAL(find_count(not_and_nested_description, not_text), 1); - - std::string and_true_description = queries->at(13).get_description(); - CHECK_EQUAL(find_count(and_true_description, "and"), 1); - CHECK_EQUAL(find_count(and_true_description, "TRUEPREDICATE"), 1); - - std::string and_false_description = queries->at(14).get_description(); - CHECK_EQUAL(find_count(and_false_description, "and"), 1); - CHECK_EQUAL(find_count(and_false_description, "FALSEPREDICATE"), 1); -} - - -TEST(Metrics_LinkQueries) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - DBRef sg = DB::create(*hist, path, options); - populate(sg); - - std::string person_table_name = "person"; - std::string pet_table_name = "pet"; - - auto wt = sg->start_write(); - TableRef person = wt->get_table(person_table_name); - TableRef pet = wt->get_table(pet_table_name); - CHECK(bool(person)); - - CHECK_EQUAL(person->get_column_count(), 8); - std::vector column_names; - for (auto col : person->get_column_keys()) { - column_names.push_back(person->get_column_name(col)); - } - - std::string pet_link_col_name = "owner"; - ColKey col_owner = pet->get_column_key(pet_link_col_name); - auto col_age = person->get_column_key("age"); - - Query q0 = pet->column(col_owner).is_null(); - Query q1 = pet->column(col_owner).is_not_null(); - Query q2 = pet->column(col_owner).count() == 1; - Query q3 = pet->column(col_owner, person->column(col_age) >= 27).count() == 1; - - q0.find_all(); - q1.find_all(); - q2.find_all(); - q3.find_all(); - - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - std::unique_ptr queries = metrics->take_queries(); - CHECK(queries); - - CHECK_EQUAL(queries->size(), 4); - - std::string null_links_description = queries->at(0).get_description(); - CHECK_EQUAL(find_count(null_links_description, "NULL"), 1); - CHECK_EQUAL(find_count(null_links_description, pet_link_col_name), 1); - - std::string not_null_links_description = queries->at(1).get_description(); - CHECK_EQUAL(find_count(not_null_links_description, "NULL"), 1); - CHECK_EQUAL(find_count(not_null_links_description, "!"), 1); - CHECK_EQUAL(find_count(not_null_links_description, pet_link_col_name), 1); - - std::string count_link_description = queries->at(2).get_description(); - CHECK_EQUAL(find_count(count_link_description, "@count"), 1); - CHECK_EQUAL(find_count(count_link_description, pet_link_col_name), 1); - CHECK_EQUAL(find_count(count_link_description, "=="), 1); - - std::string link_subquery_description = queries->at(3).get_description(); - CHECK_EQUAL(find_count(link_subquery_description, "@count"), 1); - CHECK_EQUAL(find_count(link_subquery_description, pet_link_col_name), 1); - CHECK_EQUAL(find_count(link_subquery_description, "=="), 1); - CHECK_EQUAL(find_count(link_subquery_description, column_names[0]), 1); - CHECK_EQUAL(find_count(link_subquery_description, ">"), 1); -} - - -TEST(Metrics_LinkListQueries) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - DBRef sg = DB::create(*hist, path, options); - populate(sg); - - std::string person_table_name = "person"; - std::string pet_table_name = "pet"; - - auto wt = sg->start_write(); - TableRef person = wt->get_table(person_table_name); - wt->get_table(pet_table_name); - CHECK(bool(person)); - - CHECK_EQUAL(person->get_column_count(), 8); - std::map column_names; - for (auto col : person->get_column_keys()) { - column_names[col] = person->get_column_name(col); - } - - Obj p0 = person->get_object(0); - - ColKey ll_col_key = person->get_column_key("owes_coffee_to"); - auto col_name = person->get_column_key("name"); - auto col_paid = person->get_column_key("paid"); - - Query q0 = person->column(ll_col_key).is_null(); - Query q1 = person->column(ll_col_key).is_not_null(); - Query q2 = person->column(ll_col_key).count() == 1; - Query q3 = person->column(ll_col_key) == p0; - Query q4 = person->column(ll_col_key).column(col_paid).sum() >= 1; - Query q5 = person->column(ll_col_key, person->column(col_name) == "Bob").count() == 1; - - q0.find_all(); - q1.find_all(); - q2.find_all(); - q3.find_all(); - q4.find_all(); - q5.find_all(); - - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - std::unique_ptr queries = metrics->take_queries(); - CHECK(queries); - - CHECK_EQUAL(queries->size(), 6); - - std::string null_links_description = queries->at(0).get_description(); - CHECK_EQUAL(find_count(null_links_description, "NULL"), 1); - CHECK_EQUAL(find_count(null_links_description, column_names[ll_col_key]), 1); - - std::string not_null_links_description = queries->at(1).get_description(); - CHECK_EQUAL(find_count(not_null_links_description, "NULL"), 1); - CHECK_EQUAL(find_count(not_null_links_description, "!"), 1); - CHECK_EQUAL(find_count(not_null_links_description, column_names[ll_col_key]), 1); - - std::string count_link_description = queries->at(2).get_description(); - CHECK_EQUAL(find_count(count_link_description, "@count"), 1); - CHECK_EQUAL(find_count(count_link_description, column_names[ll_col_key]), 1); - CHECK_EQUAL(find_count(count_link_description, "=="), 1); - - std::string links_description = queries->at(3).get_description(); - CHECK_EQUAL(find_count(links_description, "L0:0"), 1); - CHECK_EQUAL(find_count(links_description, column_names[ll_col_key]), 1); - CHECK_EQUAL(find_count(links_description, "=="), 1); - - std::string sum_link_description = queries->at(4).get_description(); - CHECK_EQUAL(find_count(sum_link_description, "@sum"), 1); - CHECK_EQUAL(find_count(sum_link_description, column_names[ll_col_key]), 1); - CHECK_EQUAL(find_count(sum_link_description, column_names[col_paid]), 1); - // the query system can choose to flip the argument order and operators so that >= might be <= - CHECK_EQUAL(find_count(sum_link_description, "<=") + find_count(sum_link_description, ">="), 1); - - std::string link_subquery_description = queries->at(5).get_description(); - CHECK_EQUAL(find_count(link_subquery_description, "@count"), 1); - CHECK_EQUAL(find_count(link_subquery_description, column_names[ll_col_key]), 1); - CHECK_EQUAL(find_count(link_subquery_description, "=="), 2); - CHECK_EQUAL(find_count(link_subquery_description, column_names[col_name]), 1); -} - - -TEST(Metrics_SubQueries) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - DBRef sg = DB::create(*hist, path, options); - - auto wt = sg->start_write(); - - std::string table_name = "table"; - std::string int_col_name = "integers"; - std::string str_col_name = "strings"; - - TableRef table = wt->add_table(table_name); - - auto col_list_int = table->add_column_list(type_Int, int_col_name); - auto col_list_string = table->add_column_list(type_String, str_col_name, true); - auto col_other = table->add_column(type_String, "other"); - - std::vector keys; - table->create_objects(4, keys); - - // see Query_SubtableExpression - auto set_int_list = [](LstPtr list, const std::vector& value_list) { - list->clear(); - for (auto i : value_list) { - list->add(i); - } - }; - auto set_string_list = [](LstPtr list, const std::vector& value_list) { - list->clear(); - for (auto i : value_list) { - if (i < 100) { - std::string str("Str_"); - str += util::to_string(i); - list->add(StringData(str)); - } - else { - list->add(StringData()); - } - } - }; - set_int_list(table->get_object(keys[0]).get_list_ptr(col_list_int), std::vector({0, 1})); - set_int_list(table->get_object(keys[1]).get_list_ptr(col_list_int), std::vector({2, 3, 4, 5})); - set_int_list(table->get_object(keys[2]).get_list_ptr(col_list_int), std::vector({6, 7, 8, 9})); - set_int_list(table->get_object(keys[3]).get_list_ptr(col_list_int), std::vector({})); - - set_string_list(table->get_object(keys[0]).get_list_ptr(col_list_string), std::vector({0, 1})); - set_string_list(table->get_object(keys[1]).get_list_ptr(col_list_string), std::vector({2, 3, 4, 5})); - set_string_list(table->get_object(keys[2]).get_list_ptr(col_list_string), - std::vector({6, 7, 100, 8, 9})); - table->get_object(keys[0]).set(col_other, StringData("foo")); - table->get_object(keys[1]).set(col_other, StringData("str")); - table->get_object(keys[2]).set(col_other, StringData("str_9_baa")); - - Query q0 = table->column>(col_list_int) == 10; - Query q1 = table->column>(col_list_int).max() > 5; - Query q2 = table->column>(col_list_string).begins_with("Str"); - Query q3 = table->column>(col_list_string) == "Str_0"; - - q0.find_all(); - q1.find_all(); - q2.find_all(); - q3.find_all(); - - wt->commit(); - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - std::unique_ptr queries = metrics->take_queries(); - CHECK(queries); - - CHECK_EQUAL(queries->size(), 4); - - std::string int_equal_description = queries->at(0).get_description(); - CHECK_EQUAL(int_equal_description, "10 == integers"); - - std::string int_max_description = queries->at(1).get_description(); - CHECK_EQUAL(int_max_description, "5 < integers.@max"); - - std::string str_begins_description = queries->at(2).get_description(); - CHECK_EQUAL(str_begins_description, "strings BEGINSWITH \"Str\""); - - std::string str_equal_description = queries->at(3).get_description(); - CHECK_EQUAL(str_equal_description, "\"Str_0\" == strings"); -} - -NONCONCURRENT_TEST(Metrics_TransactionTimings) -{ - ColKey col; - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - DBRef sg = DB::create(*hist, path, options); - CHECK(sg->get_metrics()); - { - auto wt = sg->start_write(); - auto table = wt->add_table("table"); - col = table->add_column(type_Int, "first"); - std::vector keys; - table->create_objects(10, keys); - wt->commit(); - } - { - auto rt = sg->start_read(); - auto table = rt->get_table("table"); - CHECK(bool(table)); - Query query = table->column(col) == 0; - query.count(); - rt->end_read(); - } - - using namespace std::literals::chrono_literals; - { - ReadTransaction rt(sg); - std::this_thread::sleep_for(60ms); - } - { - WriteTransaction wt(sg); - TableRef t = wt.get_table("table"); - t->create_object(); - std::this_thread::sleep_for(80ms); - wt.commit(); - } - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - CHECK_NOT_EQUAL(metrics->num_query_metrics(), 0); - CHECK_NOT_EQUAL(metrics->num_transaction_metrics(), 0); - - std::unique_ptr transactions = metrics->take_transactions(); - CHECK(transactions); - CHECK_EQUAL(metrics->num_transaction_metrics(), 0); - - CHECK_EQUAL(transactions->size(), 4); - - for (auto t : *transactions) { - CHECK_GREATER(t.get_transaction_time_nanoseconds(), 0); - - if (t.get_transaction_type() == TransactionInfo::read_transaction) { - CHECK_EQUAL(t.get_fsync_time_nanoseconds(), 0.0); - CHECK_EQUAL(t.get_write_time_nanoseconds(), 0.0); - } - else { - if (!get_disable_sync_to_disk()) { - CHECK_NOT_EQUAL(t.get_fsync_time_nanoseconds(), 0.0); - } - CHECK_NOT_EQUAL(t.get_write_time_nanoseconds(), 0.0); - CHECK_LESS(t.get_fsync_time_nanoseconds(), t.get_transaction_time_nanoseconds()); - CHECK_LESS(t.get_write_time_nanoseconds(), t.get_transaction_time_nanoseconds()); - } - } - // test that the timings reported are in the right ballpark - // read transaction - CHECK_EQUAL(transactions->at(2).get_transaction_type(), - metrics::TransactionInfo::TransactionType::read_transaction); - CHECK_GREATER(transactions->at(2).get_transaction_time_nanoseconds(), 1'000); // > 1us - CHECK_LESS(transactions->at(2).get_transaction_time_nanoseconds(), 2'000'000'000); // < 2s - CHECK_EQUAL(transactions->at(2).get_fsync_time_nanoseconds(), 0); // no fsync on read - // write transaction - CHECK_EQUAL(transactions->at(3).get_transaction_type(), - metrics::TransactionInfo::TransactionType::write_transaction); - CHECK_GREATER(transactions->at(3).get_transaction_time_nanoseconds(), 10'000); // > 10us - CHECK_LESS(transactions->at(3).get_transaction_time_nanoseconds(), 2'000'000'000); // < 2s - // TODO: Investigate why this check is failing, since it sometimes fails and, since this is a - // non-concurrent test, the sync_to_disk setting should not change during the execution of this test. -#if 0 - if (!get_disable_sync_to_disk()) { - // This check returns 0 for get_fsync_time_nanoseconds if sync to disk is disabled - CHECK_GREATER(transactions->at(3).get_fsync_time_nanoseconds(), 0); // fsync on write takes some time - } -#endif -} - - -TEST(Metrics_TransactionData) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - DBRef sg = DB::create(*hist, path, options); - populate(sg); - - { - ReadTransaction rt(sg); - } - { - auto wt = sg->start_write(); - auto table_keys = wt->get_table_keys(); - TableRef t0 = wt->get_table(table_keys[0]); - TableRef t1 = wt->get_table(table_keys[1]); - std::vector keys; - t0->create_objects(3, keys); - t1->create_objects(7, keys); - wt->commit(); - } - - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - - std::unique_ptr transactions = metrics->take_transactions(); - CHECK(transactions); - CHECK_EQUAL(metrics->num_transaction_metrics(), 0); - - CHECK_EQUAL(transactions->size(), 3); - - CHECK_EQUAL(transactions->at(0).get_total_objects(), 11); - CHECK_EQUAL(transactions->at(1).get_total_objects(), 11); - CHECK_EQUAL(transactions->at(2).get_total_objects(), 11 + 3 + 7); -} - -TEST(Metrics_TransactionVersions) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - DBRef sg = DB::create(*hist, path, options); - populate(sg); - const size_t num_writes_while_pinned = 10; - TableKey tk0; - TableKey tk1; - { - auto rt = sg->start_read(); - auto table_keys = rt->get_table_keys(); - tk0 = table_keys[0]; - tk1 = table_keys[1]; - } - { - auto wt = sg->start_write(); - TableRef t0 = wt->get_table(tk0); - TableRef t1 = wt->get_table(tk1); - std::vector keys; - t0->create_objects(3, keys); - t1->create_objects(7, keys); - wt->commit(); - } - { - std::unique_ptr hist2(make_in_realm_history()); - DBRef sg2 = DB::create(*hist2, path, options); - - // Pin this version. Note that since this read transaction is against a different shared group - // it doesn't get tracked in the transaction metrics of the original shared group. - ReadTransaction rt(sg2); - - for (size_t i = 0; i < num_writes_while_pinned; ++i) { - auto wt = sg->start_write(); - TableRef t0 = wt->get_table(tk0); - t0->create_object(); - wt->commit(); - } - } - - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - - std::unique_ptr transactions = metrics->take_transactions(); - CHECK(transactions); - CHECK_EQUAL(metrics->num_transaction_metrics(), 0); - - CHECK_EQUAL(transactions->size(), 3 + num_writes_while_pinned); - - CHECK_EQUAL(transactions->at(0).get_num_available_versions(), 2); - CHECK_EQUAL(transactions->at(1).get_num_available_versions(), 2); - CHECK_EQUAL(transactions->at(2).get_num_available_versions(), 2); - CHECK_EQUAL(transactions->at(3).get_num_available_versions(), 2); - - // from here intermediate versions are reclaimed, leaving us - // with 3 active versions regardless of the number of commits - for (size_t i = 1; i < num_writes_while_pinned; ++i) { - CHECK_EQUAL(transactions->at(3 + i).get_num_available_versions(), 3); - } -} - -TEST(Metrics_MaxNumTransactionsIsNotExceeded) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - options.metrics_buffer_size = 10; - auto sg = DB::create(*hist, path, options); - populate(sg); // 1 - { - ReadTransaction rt(sg); // 2 - } - { - WriteTransaction wt(sg); // 3 - TableRef t0 = wt.get_table("person"); - TableRef t1 = wt.get_table("pet"); - for (int i = 0; i < 3; i++) { - t0->create_object(); - } - for (int i = 0; i < 7; i++) { - t1->create_object(); - } - wt.commit(); - } - - for (size_t i = 0; i < options.metrics_buffer_size; ++i) { - ReadTransaction rt(sg); - } - - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - - CHECK_EQUAL(metrics->num_query_metrics(), 0); - CHECK_EQUAL(metrics->num_transaction_metrics(), options.metrics_buffer_size); - std::unique_ptr transactions = metrics->take_transactions(); - CHECK(transactions); - for (auto transaction : *transactions) { - CHECK_EQUAL(transaction.get_transaction_type(), - realm::metrics::TransactionInfo::TransactionType::read_transaction); - } -} - -TEST(Metrics_MaxNumQueriesIsNotExceeded) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - options.metrics_buffer_size = 10; - auto sg = DB::create(*hist, path, options); - - { - auto tr = sg->start_write(); - auto table = tr->add_table("table"); - table->add_column(type_Int, "col_int"); - for (int i = 0; i < 10; i++) { - table->create_object(); - } - tr->commit(); - } - - { - auto rt = sg->start_read(); - auto table = rt->get_table("table"); - auto int_col = table->get_column_key("col_int"); - CHECK(bool(table)); - Query query = table->column(int_col) == 0; - for (size_t i = 0; i < 2 * options.metrics_buffer_size; ++i) { - query.find(); - } - } - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - CHECK_EQUAL(metrics->num_query_metrics(), options.metrics_buffer_size); -} - -// The number of decrypted pages is updated periodically by the governor. -// To test this, we need our own governor implementation which does not reclaim pages but runs at least once. -class NoPageReclaimGovernor : public realm::util::PageReclaimGovernor { -public: - NoPageReclaimGovernor() - { - has_run_twice = will_run.get_future(); - } - - util::UniqueFunction current_target_getter(size_t) override - { - return []() { - return realm::util::PageReclaimGovernor::no_match; - }; - } - - void report_target_result(int64_t) override - { - if (run_count == 2) { // need to run twice before we're done - will_run.set_value(); - } - ++run_count; - } - - std::future has_run_twice; - std::promise will_run; - int run_count = 0; -}; - -// this test relies on the global state of the number of decrypted pages and therefore must be run in isolation -NONCONCURRENT_TEST(Metrics_NumDecryptedPagesWithoutEncryption) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(nullptr); - options.enable_metrics = true; - options.metrics_buffer_size = 10; - auto sg = DB::create(*hist, path, options); - - { - auto tr = sg->start_write(); - tr->add_table("table"); - -#if REALM_ENABLE_ENCRYPTION - // we need this here because other unit tests might be using encryption and we need a guarantee - // that the global pages are from this shared group only. - NoPageReclaimGovernor gov; - realm::util::set_page_reclaim_governor(&gov); - // the remainder of the test suite should use the default. - auto on_exit = make_scope_exit([]() noexcept { - realm::util::set_page_reclaim_governor_to_default(); - }); - CHECK(gov.has_run_twice.valid()); - REALM_ASSERT_RELEASE(gov.has_run_twice.wait_for(std::chrono::seconds(30)) == std::future_status::ready); -#endif - - tr->commit(); - } - - { - auto rt = sg->start_read(); - } - - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - - CHECK_EQUAL(metrics->num_transaction_metrics(), 2); - std::unique_ptr transactions = metrics->take_transactions(); - CHECK(transactions); - CHECK_EQUAL(transactions->size(), 2); - CHECK_EQUAL(transactions->at(0).get_transaction_type(), - realm::metrics::TransactionInfo::TransactionType::write_transaction); - CHECK_EQUAL(transactions->at(0).get_num_decrypted_pages(), 0); - CHECK_EQUAL(transactions->at(1).get_transaction_type(), - realm::metrics::TransactionInfo::TransactionType::read_transaction); - CHECK_EQUAL(transactions->at(1).get_num_decrypted_pages(), 0); -} - -// this test relies on the global state of the number of decrypted pages and therefore must be run in isolation -NONCONCURRENT_TEST_IF(Metrics_NumDecryptedPagesWithEncryption, REALM_ENABLE_ENCRYPTION) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key(true)); - options.enable_metrics = true; - options.metrics_buffer_size = 10; - auto sg = DB::create(*hist, path, options); - - { - auto tr = sg->start_write(); - tr->add_table("table"); - -#if REALM_ENABLE_ENCRYPTION - NoPageReclaimGovernor gov; - realm::util::set_page_reclaim_governor(&gov); - // the remainder of the test suite should use the default. - auto on_exit = make_scope_exit([]() noexcept { - realm::util::set_page_reclaim_governor_to_default(); - }); - CHECK(gov.has_run_twice.valid()); - REALM_ASSERT_RELEASE(gov.has_run_twice.wait_for(std::chrono::seconds(30)) == std::future_status::ready); -#endif - - tr->commit(); - } - - { - auto rt = sg->start_read(); - } - - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - - CHECK_EQUAL(metrics->num_transaction_metrics(), 2); - std::unique_ptr transactions = metrics->take_transactions(); - CHECK(transactions); - CHECK_EQUAL(transactions->size(), 2); - CHECK_EQUAL(transactions->at(0).get_transaction_type(), - realm::metrics::TransactionInfo::TransactionType::write_transaction); - CHECK_EQUAL(transactions->at(0).get_num_decrypted_pages(), 1); - CHECK_EQUAL(transactions->at(1).get_transaction_type(), - realm::metrics::TransactionInfo::TransactionType::read_transaction); - CHECK_EQUAL(transactions->at(1).get_num_decrypted_pages(), 1); -} - -TEST(Metrics_MemoryChecks) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(nullptr); - options.enable_metrics = true; - options.metrics_buffer_size = 10; - auto sg = DB::create(*hist, path, options); - populate(sg); - - { - auto rt = sg->start_read(); - } - - std::shared_ptr metrics = sg->get_metrics(); - CHECK(metrics); - - CHECK_EQUAL(metrics->num_transaction_metrics(), 2); - std::unique_ptr transactions = metrics->take_transactions(); - CHECK(transactions); - - for (auto transaction : *transactions) { - CHECK_GREATER(transaction.get_disk_size(), 0); - CHECK_GREATER(transaction.get_free_space(), 0); - } -} - -#else // REALM_METRICS - -TEST(Metrics_APIAvailability) -{ - SHARED_GROUP_TEST_PATH(path); - std::unique_ptr hist(make_in_realm_history()); - DBOptions options(crypt_key()); - options.enable_metrics = true; - DBRef sg = DB::create(*hist, path, options); - CHECK(!sg->get_metrics()); - { - auto tr = sg->start_write(); - auto table = tr->add_table("table"); - table->add_column(type_Int, "first"); - for (int i = 0; i < 10; i++) { - table->create_object(); - } - tr->commit(); - } - - { - ReadTransaction rt(sg); - auto table = rt.get_table("table"); - auto col = table->get_column_key("first"); - CHECK(bool(table)); - Query q = table->column(col) == 0; - q.count(); - } - std::shared_ptr metrics = sg->get_metrics(); - - // the following will never execute since when REALM_METRICS is undefined, - // then sg.get_metrics() will always return a nullptr, however, the purpose - // of the remainder of the test is to ensure that all of the methods below - // are still accessible at compile time so that users of core do not need to check - // REALM_METRICS, but can use a core with or without the flag in the same way. - if (metrics) { - CHECK_EQUAL(metrics->num_transaction_metrics(), 0); - CHECK_EQUAL(metrics->num_query_metrics(), 0); - std::unique_ptr transactions = metrics->take_transactions(); - - if (transactions) { - for (auto transaction : *transactions) { - transaction.get_disk_size(); - transaction.get_free_space(); - transaction.get_transaction_time(); - transaction.get_fsync_time(); - transaction.get_write_time(); - transaction.get_disk_size(); - transaction.get_free_space(); - transaction.get_total_objects(); - transaction.get_num_available_versions(); - transaction.get_num_decrypted_pages(); - } - } - std::unique_ptr queries = metrics->take_queries(); - if (queries) { - for (auto query : *queries) { - query.get_description(); - query.get_table_name(); - query.get_type(); - query.get_query_time(); - } - } - } -} - -#endif // REALM_METRICS -#endif // TEST_METRICS diff --git a/test/test_parser.cpp b/test/test_parser.cpp index 20a6e4db435..5b0ee14c1d9 100644 --- a/test/test_parser.cpp +++ b/test/test_parser.cpp @@ -70,7 +70,6 @@ #include using namespace realm; -using namespace realm::metrics; using namespace realm::test_util; using namespace realm::util; diff --git a/test/testsettings.hpp b/test/testsettings.hpp index c799e7cf26c..ab159805840 100644 --- a/test/testsettings.hpp +++ b/test/testsettings.hpp @@ -59,7 +59,6 @@ #define TEST_UPGRADE #define TEST_INDEX_STRING #define TEST_LANG_BIND_HELPER -#define TEST_METRICS #define TEST_PARSER #define TEST_QUERY #define TEST_SHARED From b37bbddff7e1479a16b4297fdcd8adb26adc52c8 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 21 Sep 2023 00:31:16 -0700 Subject: [PATCH 30/34] Add macCatalyst to the platforms which link Foundation (#6838) This is required when SPM decides to link core as a dynamic library. --- CHANGELOG.md | 1 + Package.swift | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bcd5d45c84..10d18d8d8cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ([PR #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0) * Reading existing logged-in users on app startup from the sync metadata Realm performed three no-op writes per user on the metadata Realm ([PR #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0). * If a user was logged out while an access token refresh was in progress, the refresh completing would mark the user as logged in again and the user would be in an inconsistent state ([PR #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0). +* The Swift package failed to link required libraries when building for macCatalyst. ### Breaking changes * SyncUser::provider_type() and realm_user_get_auth_provider() have been removed. Users don't have provider types; identities do. `SyncUser::is_anonymous()` is a more correct version of checking if the provider type is anonymous ([PR #6837](https://github.com/realm/realm-core/pull/6837)). diff --git a/Package.swift b/Package.swift index 816de82e813..22c5c486fbe 100644 --- a/Package.swift +++ b/Package.swift @@ -425,8 +425,8 @@ let package = Package( linkerSettings: [ .linkedLibrary("compression"), .linkedLibrary("z"), - .linkedFramework("Foundation", .when(platforms: [.macOS, .iOS, .tvOS, .watchOS])), - .linkedFramework("Security", .when(platforms: [.macOS, .iOS, .tvOS, .watchOS])), + .linkedFramework("Foundation", .when(platforms: [.macOS, .iOS, .tvOS, .watchOS, .macCatalyst])), + .linkedFramework("Security", .when(platforms: [.macOS, .iOS, .tvOS, .watchOS, .macCatalyst])), ]), .target( name: "RealmQueryParser", From f2e65a1796a2b1ce239b4a86b4d7c93aba695824 Mon Sep 17 00:00:00 2001 From: James Stone Date: Thu, 21 Sep 2023 10:42:05 -0700 Subject: [PATCH 31/34] instead of throwing an exception on ill-formatted geospatial data, queries will now ignore those objects (#6989) --- CHANGELOG.md | 1 + src/realm/geospatial.cpp | 6 ++++-- test/test_query_geo.cpp | 34 +++++++++++++++++++++++++++++----- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10d18d8d8cd..a025da54478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ([PR #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0) * Reading existing logged-in users on app startup from the sync metadata Realm performed three no-op writes per user on the metadata Realm ([PR #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0). * If a user was logged out while an access token refresh was in progress, the refresh completing would mark the user as logged in again and the user would be in an inconsistent state ([PR #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0). +* If querying over a geospatial dataset that had some objects with a type property set to something other than 'Point' (case insensitive) an exception would have been thrown. Instead of disrupting the query, those objects are now just ignored. ([PR 6989](https://github.com/realm/realm-core/issues/6989), since the introduction of geospatial) * The Swift package failed to link required libraries when building for macCatalyst. ### Breaking changes diff --git a/src/realm/geospatial.cpp b/src/realm/geospatial.cpp index dbb7c38c817..419482fc5b1 100644 --- a/src/realm/geospatial.cpp +++ b/src/realm/geospatial.cpp @@ -154,7 +154,7 @@ std::optional Geospatial::point_from_obj(const Obj& obj, ColKey type_c } if (!type_is_valid(obj.get(type_col))) { - throw IllegalOperation("The only Geospatial type currently supported is 'point'"); + return {}; // the only geospatial type currently supported is 'Point' } Lst coords = obj.get_list(coords_col); @@ -211,7 +211,9 @@ void Geospatial::assign_to(Obj& link) const return; } if (type != Type::Point) { - throw IllegalOperation("The only Geospatial type currently supported for storage is 'point'"); + throw IllegalOperation(util::format("Attempting to store a '%1' in class '%2' but the only Geospatial type " + "currently supported for storage is 'Point'", + get_type_string(), link.get_table()->get_class_name())); } auto&& point = get(); link.set(type_col, get_type_string()); diff --git a/test/test_query_geo.cpp b/test/test_query_geo.cpp index 953df35b70c..c83c83eae11 100644 --- a/test/test_query_geo.cpp +++ b/test/test_query_geo.cpp @@ -112,10 +112,13 @@ TEST(Geospatial_Assignment) Geospatial geo_box(GeoBox{GeoPoint{1.1, 2.2}, GeoPoint{3.3, 4.4}}); CHECK(*GeoBox::from_polygon(geo_box.get().to_polygon()) == geo_box.get()); - std::string_view err_msg = "The only Geospatial type currently supported for storage is 'point'"; - CHECK_THROW_CONTAINING_MESSAGE(obj.set(location_column_key, geo_box), err_msg); + std::string_view err_msg_box = "Attempting to store a 'Box' in class 'Location' but the only Geospatial type " + "currently supported for storage is 'Point'"; + CHECK_THROW_CONTAINING_MESSAGE(obj.set(location_column_key, geo_box), err_msg_box); Geospatial geo_circle(GeoCircle{10, GeoPoint{1.1, 2.2}}); - CHECK_THROW_CONTAINING_MESSAGE(obj.set(location_column_key, geo_circle), err_msg); + std::string_view err_msg_circle = "Attempting to store a 'Circle' in class 'Location' but the only Geospatial " + "type currently supported for storage is 'Point'"; + CHECK_THROW_CONTAINING_MESSAGE(obj.set(location_column_key, geo_circle), err_msg_circle); } TEST(Geospatial_invalid_format) @@ -137,10 +140,31 @@ TEST(Geospatial_invalid_format) TEST(Query_GeoWithinBasics) { Group g; - std::vector data = {GeoPoint{-2, -1}, GeoPoint{-1, -2}, GeoPoint{0, 0}, - GeoPoint{0.5, 0.5}, GeoPoint{1, 1}, GeoPoint{2, 2}}; + std::vector data = {GeoPoint{-2, -1}, GeoPoint{-1, -2}, GeoPoint{0, 0}, GeoPoint{0.5, 0.5}, + GeoPoint{1, 1}, GeoPoint{2, 2, 2}, GeoPoint()}; TableRef table = setup_with_points(g, data); ColKey location_column_key = table->get_column_key("location"); + // an object with null link location + table->create_object_with_primary_key(-42); + // an object with a location that doesn't have properties set on the point + Obj invalid_point = table->create_object_with_primary_key(-43); + invalid_point.create_and_set_linked_object(location_column_key); + // an object with the correct 'Point' but invalid coordinates + Obj invalid_coords = table->create_object_with_primary_key(-44); + Obj embedded_invalid = invalid_coords.create_and_set_linked_object(location_column_key); + embedded_invalid.set(embedded_invalid.get_table()->get_column_key("type"), "Point"); + // an object with 4 elements in the coordinate list + Obj excess_coords = table->create_object_with_primary_key(-44); + Obj embedded_excess = excess_coords.create_and_set_linked_object(location_column_key); + embedded_excess.set(embedded_excess.get_table()->get_column_key("type"), "Point"); + auto list = embedded_excess.get_list(embedded_excess.get_table()->get_column_key("coordinates")); + list.add(2); + list.add(2); + list.add(2); + list.add(2); + Geospatial geo_excess = excess_coords.get(location_column_key); + CHECK(geo_excess.is_valid() == Status::OK()); + for (size_t i = 0; i < data.size(); ++i) { Obj obj = table->get_object_with_primary_key(int64_t(i)); CHECK(obj); From b94a9eecc22466d38b829d784eaa047113e72a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Tue, 19 Sep 2023 14:06:59 +0200 Subject: [PATCH 32/34] Simplify Logger class a bit Logger::m_base_logger_ptr seems not to be used in the class itself. The member is added to the sub-classes that need it. get/set level_threshold need not be virtual is we remove support for NullLogger. --- src/realm/db.cpp | 12 ++++--- src/realm/db.hpp | 2 +- src/realm/util/logger.hpp | 47 +++++++--------------------- test/object-store/util/test_file.cpp | 8 +---- test/util/compare_groups.cpp | 8 +---- 5 files changed, 22 insertions(+), 55 deletions(-) diff --git a/src/realm/db.cpp b/src/realm/db.cpp index 3952efc097e..67e04cf203b 100644 --- a/src/realm/db.cpp +++ b/src/realm/db.cpp @@ -1476,8 +1476,9 @@ void DB::open(Replication& repl, const std::string& file, const DBOptions& optio class DBLogger : public Logger { public: DBLogger(const std::shared_ptr& base_logger, unsigned hash) noexcept - : Logger(base_logger) + : Logger(*base_logger) , m_hash(hash) + , m_base_logger_ptr(base_logger) { } @@ -1486,18 +1487,19 @@ class DBLogger : public Logger { { std::ostringstream ostr; auto id = std::this_thread::get_id(); - ostr << "DB: " << m_hash << " Thread " << id << ": "; - Logger::do_log(*m_base_logger_ptr, level, ostr.str() + message); + ostr << "DB: " << m_hash << " Thread " << id << ": " << message; + Logger::do_log(*m_base_logger_ptr, level, ostr.str()); } private: unsigned m_hash; + std::shared_ptr m_base_logger_ptr; }; void DB::set_logger(const std::shared_ptr& logger) noexcept { if (logger) - m_logger = std::make_shared(logger, m_log_id); + m_logger = std::make_unique(logger, m_log_id); } void DB::open(Replication& repl, const DBOptions options) @@ -1511,7 +1513,7 @@ void DB::open(Replication& repl, const DBOptions options) set_logger(options.logger); m_replication->set_logger(m_logger.get()); if (m_logger) - m_logger->log(util::Logger::Level::detail, "Open memory-only realm"); + m_logger->detail("Open memory-only realm"); auto hist_type = repl.get_history_type(); m_in_memory_info = diff --git a/src/realm/db.hpp b/src/realm/db.hpp index bf8d419b725..eedde4823dd 100644 --- a/src/realm/db.hpp +++ b/src/realm/db.hpp @@ -509,7 +509,7 @@ class DB : public std::enable_shared_from_this { std::function m_upgrade_callback; std::shared_ptr m_metrics; std::unique_ptr m_commit_helper; - std::shared_ptr m_logger; + std::unique_ptr m_logger; bool m_is_sync_agent = false; // Id for this DB to be used in logging. We will just use some bits from the pointer. // The path cannot be used as this would not allow us to distinguish between two DBs opening diff --git a/src/realm/util/logger.hpp b/src/realm/util/logger.hpp index eb1d10d54e2..7e1e6bed654 100644 --- a/src/realm/util/logger.hpp +++ b/src/realm/util/logger.hpp @@ -82,13 +82,13 @@ class Logger { template void log(Level, const char* message, Params&&...); - virtual Level get_level_threshold() const noexcept + Level get_level_threshold() const noexcept { // Don't need strict ordering, mainly that the gets/sets are atomic return m_level_threshold.load(std::memory_order_relaxed); } - virtual void set_level_threshold(Level level) noexcept + void set_level_threshold(Level level) noexcept { // Don't need strict ordering, mainly that the gets/sets are atomic m_level_threshold.store(level, std::memory_order_relaxed); @@ -109,9 +109,6 @@ class Logger { static const std::string_view level_to_string(Level level) noexcept; protected: - // Used by subclasses that link to a base logger - std::shared_ptr m_base_logger_ptr; - // Shared level threshold for subclasses that link to a base logger // See PrefixLogger and ThreadSafeLogger std::atomic& m_level_threshold; @@ -128,9 +125,8 @@ class Logger { { } - explicit Logger(const std::shared_ptr& base_logger) noexcept - : m_base_logger_ptr{base_logger} - , m_level_threshold{m_base_logger_ptr->m_level_threshold} + explicit Logger(const Logger& base_logger) noexcept + : m_level_threshold{base_logger.m_level_threshold} { } @@ -229,6 +225,7 @@ class ThreadSafeLogger : public Logger { private: Mutex m_mutex; + std::shared_ptr m_base_logger_ptr; }; @@ -247,6 +244,7 @@ class PrefixLogger : public Logger { private: const std::string m_prefix; // The next logger in the chain for chained PrefixLoggers or the base_logger + std::shared_ptr m_owned_logger; Logger& m_chained_logger; }; @@ -273,29 +271,6 @@ class LocalThresholdLogger : public Logger { }; -/// A logger that essentially performs a noop when logging functions are called -/// The log level threshold for this logger is always Logger::Level::off and -/// cannot be changed. -class NullLogger : public Logger { -public: - NullLogger() - : Logger{Level::off} - { - } - - Level get_level_threshold() const noexcept override - { - return Level::off; - } - - void set_level_threshold(Level) noexcept override {} - -protected: - // Since we don't want to log anything, do_log() does nothing - void do_log(Level, const std::string&) override {} -}; - - // Implementation template @@ -459,14 +434,15 @@ inline AppendToFileLogger::AppendToFileLogger(util::File file) } inline ThreadSafeLogger::ThreadSafeLogger(const std::shared_ptr& base_logger) noexcept - : Logger(base_logger) + : Logger(*base_logger) + , m_base_logger_ptr(base_logger) { } // Construct a PrefixLogger from another PrefixLogger object for chaining the prefixes on log output inline PrefixLogger::PrefixLogger(std::string prefix, PrefixLogger& prefix_logger) noexcept // Save an alias of the base_logger shared_ptr from the passed in PrefixLogger - : Logger(prefix_logger.m_base_logger_ptr) + : Logger(prefix_logger) , m_prefix{std::move(prefix)} , m_chained_logger{prefix_logger} // do_log() writes to the chained logger { @@ -477,9 +453,10 @@ inline PrefixLogger::PrefixLogger(std::string prefix, PrefixLogger& prefix_logge // created, will point back to this logger shared_ptr for referencing the level_threshold when // logging output. inline PrefixLogger::PrefixLogger(std::string prefix, const std::shared_ptr& base_logger) noexcept - : Logger(base_logger) // Save an alias of the passed in base_logger shared_ptr + : Logger(*base_logger) // Save an alias of the passed in base_logger shared_ptr , m_prefix{std::move(prefix)} - , m_chained_logger{*base_logger} // do_log() writes to the chained logger + , m_owned_logger{base_logger} + , m_chained_logger{*m_owned_logger} // do_log() writes to the chained logger { } diff --git a/test/object-store/util/test_file.cpp b/test/object-store/util/test_file.cpp index f0f560a8864..16ec371da99 100644 --- a/test/object-store/util/test_file.cpp +++ b/test/object-store/util/test_file.cpp @@ -196,13 +196,7 @@ SyncServer::SyncServer(const SyncServer::Config& config) , m_server(m_local_root_dir, util::none, ([&] { using namespace std::literals::chrono_literals; -#if TEST_ENABLE_LOGGING - auto logger = new util::StderrLogger(realm::util::Logger::Level::TEST_LOGGING_LEVEL); - m_logger.reset(logger); -#else - // Logging is disabled, use a NullLogger to prevent printing anything - m_logger.reset(new util::NullLogger()); -#endif + m_logger = std::make_shared(realm::util::Logger::Level::TEST_LOGGING_LEVEL); sync::Server::Config c; c.logger = m_logger; diff --git a/test/util/compare_groups.cpp b/test/util/compare_groups.cpp index 1f68ba1fb1b..67f7957bc3f 100644 --- a/test/util/compare_groups.cpp +++ b/test/util/compare_groups.cpp @@ -957,12 +957,6 @@ bool compare_objects(sync::PrimaryKey& oid, const Table& table_1, const Table& t namespace realm::test_util { -bool compare_tables(const Table& table_1, const Table& table_2) -{ - util::NullLogger logger; - return compare_tables(table_1, table_2, logger); -} - bool compare_tables(const Table& table_1, const Table& table_2, util::Logger& logger) { bool equal = true; @@ -1039,7 +1033,7 @@ bool compare_tables(const Table& table_1, const Table& table_2, util::Logger& lo bool compare_groups(const Transaction& group_1, const Transaction& group_2) { - util::NullLogger logger; + util::StderrLogger logger; return compare_groups(group_1, group_2, logger); } From 7083d1018fda1d968b7dc9a3f8471e843f1c85e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Fri, 22 Sep 2023 12:34:33 +0200 Subject: [PATCH 33/34] Limiting the output when logging large string and binary values (#6986) --- src/realm/mixed.cpp | 51 +++++++++++++++++++++++++++++++++++++++ src/realm/mixed.hpp | 4 +++ src/realm/replication.cpp | 6 +++-- src/realm/util/logger.hpp | 2 ++ test/test_table.cpp | 17 +++++++++++++ 5 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/realm/mixed.cpp b/src/realm/mixed.cpp index a1dccfb2332..0f92f3750a8 100644 --- a/src/realm/mixed.cpp +++ b/src/realm/mixed.cpp @@ -756,6 +756,57 @@ StringData Mixed::get_index_data(std::array& buffer) const noexcept return {}; } +std::string Mixed::to_string(size_t max_size) const noexcept +{ + std::ostringstream ostr; + if (is_type(type_String)) { + std::string ret = "\""; + if (string_val.size() <= max_size) { + ret += std::string(string_val); + } + else { + ret += std::string(StringData(string_val.data(), max_size)) + " ..."; + } + ret += "\""; + return ret; + } + else if (is_type(type_Binary)) { + static constexpr int size_one_hex_out = 3; + static char hex_chars[] = "0123456789ABCDEF"; + auto out_hex = [&ostr](char c) { + ostr << hex_chars[c >> 4]; + ostr << hex_chars[c & 0xF]; + ostr << ' '; + }; + + auto sz = binary_val.size(); + bool capped = false; + size_t out_size = 0; + + ostr << '"'; + for (size_t n = 0; n < sz; n++) { + out_size += size_one_hex_out; + if (out_size > max_size) { + capped = true; + break; + } + out_hex(binary_val[n]); + } + if (capped) { + ostr << "..."; + } + ostr << '"'; + } + else if (is_type(type_Timestamp)) { + char buffer[32]; + return date_val.to_string(buffer); + } + else { + ostr << *this; + } + return ostr.str(); +} + void Mixed::use_buffer(std::string& buf) noexcept { if (is_null()) { diff --git a/src/realm/mixed.hpp b/src/realm/mixed.hpp index 77ec360ec07..c4440663f28 100644 --- a/src/realm/mixed.hpp +++ b/src/realm/mixed.hpp @@ -263,7 +263,11 @@ class Mixed { Mixed operator/(const Mixed&) const noexcept; size_t hash() const; + // Used when inserting values into index StringData get_index_data(std::array&) const noexcept; + // Used when logging values + std::string to_string(size_t max_size) const noexcept; + // Used when you need a backup buffer for string or binary value void use_buffer(std::string& buf) noexcept; void to_json(std::ostream& out, JSONOutputMode output_mode) const noexcept; diff --git a/src/realm/replication.cpp b/src/realm/replication.cpp index a05bcfff142..a230d29a798 100644 --- a/src/realm/replication.cpp +++ b/src/realm/replication.cpp @@ -232,7 +232,8 @@ void Replication::set(const Table* t, ColKey col_key, ObjKey key, Mixed value, _ } } else { - logger->log(util::Logger::Level::trace, " Set '%1' to %2", t->get_column_name(col_key), value); + logger->log(util::Logger::Level::trace, " Set '%1' to %2", t->get_column_name(col_key), + value.to_string(util::Logger::max_width_of_value)); } } } @@ -297,7 +298,8 @@ void Replication::log_collection_operation(const char* operation, const Collecti } } else { - logger->log(util::Logger::Level::trace, " %1 %2 in %3%4", operation, value, path, position); + logger->log(util::Logger::Level::trace, " %1 %2 in %3%4", operation, + value.to_string(util::Logger::max_width_of_value), path, position); } } void Replication::list_insert(const CollectionBase& list, size_t list_ndx, Mixed value, size_t) diff --git a/src/realm/util/logger.hpp b/src/realm/util/logger.hpp index 7e1e6bed654..3f4a9e251ae 100644 --- a/src/realm/util/logger.hpp +++ b/src/realm/util/logger.hpp @@ -79,6 +79,8 @@ class Logger { // this is enforced in logging.cpp. enum class Level { all = 0, trace = 1, debug = 2, detail = 3, info = 4, warn = 5, error = 6, fatal = 7, off = 8 }; + static constexpr size_t max_width_of_value = 80; + template void log(Level, const char* message, Params&&...); diff --git a/test/test_table.cpp b/test/test_table.cpp index 91377bad89a..d866b11bbb2 100644 --- a/test/test_table.cpp +++ b/test/test_table.cpp @@ -5377,14 +5377,28 @@ TEST(Table_LoggingMutations) auto t = wt->add_table_with_primary_key("foo", type_Int, "id"); col = t->add_column(type_Mixed, "any"); col_int = t->add_column(type_Int, "int"); + auto dict = t->create_object_with_primary_key(1).set_collection(col, CollectionType::Dictionary).get_dictionary(col); dict.insert("hello", "world"); + auto list = t->create_object_with_primary_key(2).set_collection(col, CollectionType::List).get_list(col); list.add(47.50); + auto set = t->create_object_with_primary_key(3).set_collection(col, CollectionType::Set).get_set(col); set.insert(false); + + std::vector str_data(90); + std::iota(str_data.begin(), str_data.end(), ' '); + t->create_object_with_primary_key(5).set_any(col, StringData(str_data.data(), str_data.size())); + + std::vector bin_data(50); + std::iota(bin_data.begin(), bin_data.end(), 0); + t->create_object_with_primary_key(6).set_any(col, BinaryData(bin_data.data(), bin_data.size())); + + t->create_object_with_primary_key(7).set_any(col, Timestamp(1695207215, 0)); + wt->commit(); } { @@ -5397,6 +5411,9 @@ TEST(Table_LoggingMutations) auto str = buffer.str(); // std::cout << str << std::endl; + CHECK(str.find("abcdefghijklmno ...") != std::string::npos); + CHECK(str.find("14 15 16 17 18 19 ...") != std::string::npos); + CHECK(str.find("2023-09-20 10:53:35") != std::string::npos); CHECK(str.find("Query::get_description() failed:") != std::string::npos); CHECK(str.find("Set 'any' to dictionary") != std::string::npos); CHECK(str.find("Set 'any' to list") != std::string::npos); From f8e1a38a4099785af597ab813ae01f9901858b65 Mon Sep 17 00:00:00 2001 From: Finn Schiermer Andersen Date: Mon, 25 Sep 2023 13:22:55 +0200 Subject: [PATCH 34/34] Prepare release 13.21 --- CHANGELOG.md | 3 +-- Package.swift | 2 +- dependencies.list | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a025da54478..b59434bb281 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,9 @@ -# NEXT RELEASE +# 13.21.0 Release notes ### Enhancements * Allow non-embedded links in asymmetric objects. ([PR #6981](https://github.com/realm/realm-core/pull/6981)) ### Fixed -* ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?) * Logging into a single user using multiple auth providers created a separate SyncUser per auth provider. This mostly worked, but had some quirks: - Sync sessions would not necessarily be associated with the specific SyncUser used to create them. As a result, querying a user for its sessions could give incorrect results, and logging one user out could close the wrong sessions. - Existing local synchronized Realm files created using version of Realm from August - November 2020 would sometimes not be opened correctly and would instead be redownloaded. diff --git a/Package.swift b/Package.swift index 22c5c486fbe..ae12db51ee0 100644 --- a/Package.swift +++ b/Package.swift @@ -3,7 +3,7 @@ import PackageDescription import Foundation -let versionStr = "13.20.1" +let versionStr = "13.21.0" let versionPieces = versionStr.split(separator: "-") let versionCompontents = versionPieces[0].split(separator: ".") let versionExtra = versionPieces.count > 1 ? versionPieces[1] : "" diff --git a/dependencies.list b/dependencies.list index 78780c2d532..9768129dd88 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-core -VERSION=13.20.1 +VERSION=13.21.0 OPENSSL_VERSION=3.0.8 ZLIB_VERSION=1.2.13 MDBREALM_TEST_SERVER_TAG=2023-08-11